@toolpath/viewer 1.3.0 → 1.5.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.
package/dist/index.js CHANGED
@@ -1,15 +1,20 @@
1
1
  import {
2
+ ANGLE_ARC_FRACTION,
3
+ ANGLE_ARC_SEGMENTS,
2
4
  AXES_PLANE_OFFSET,
3
5
  AXES_PLANE_SCALE,
6
+ AXIS_COLORS,
4
7
  CAD_CAMERA_UP,
5
8
  CANDIDATE_WEIGHT,
6
9
  CAP_HATCH,
7
10
  CHAMFER,
8
11
  CONE_AXIS,
12
+ CONTROL_SCHEME_OPTIONS,
9
13
  CadCameraControls,
10
14
  DEFAULT_FIT_MARGIN,
11
15
  DEFAULT_SECTION_NORMAL,
12
16
  DEFAULT_THEME,
17
+ DELTA_DASH_PIXELS,
13
18
  DIRECTION_COLORS,
14
19
  DISABLED_SECTION,
15
20
  DOUBLE_TAP_MS,
@@ -24,6 +29,9 @@ import {
24
29
  HIGHLIGHT_WEIGHT,
25
30
  HOVER_WEIGHT,
26
31
  MAX_FRAME_RATIO,
32
+ MEASURE_LINE_PIXELS,
33
+ MEASURE_MARKER_PIXELS,
34
+ MEASURE_RENDER_ORDER,
27
35
  MIN_FRAME_RATIO,
28
36
  NO_MODIFIERS,
29
37
  ORBIT_TARGET_COLOR,
@@ -37,12 +45,17 @@ import {
37
45
  OUTLINE_SCALE,
38
46
  PERSPECTIVE_FOV,
39
47
  PICKED_SURFACE_LABEL,
48
+ POINTS_PER_MEASUREMENT,
40
49
  PREVIEW_SCALE,
41
50
  PartMesh,
42
51
  PartReportFormatError,
43
52
  REGION_ATTRIBUTE,
53
+ SECTION_GIZMO_FRAME_SCALE,
44
54
  SECTION_RENDER_ORDER,
45
55
  SHAFT_RADIUS,
56
+ SNAP_MARKER_PIXELS,
57
+ SNAP_PIXELS,
58
+ STOCK_OBJECT,
46
59
  SectionView,
47
60
  TAP_SLOP,
48
61
  UnsupportedKernelVersionError,
@@ -50,6 +63,9 @@ import {
50
63
  VIEW_SIGNS,
51
64
  Viewer,
52
65
  adaptedUp,
66
+ angleArc,
67
+ angleArcRadius,
68
+ angleAt,
53
69
  applyCapTheme,
54
70
  applyHighlightLayers,
55
71
  applyProjection,
@@ -76,19 +92,30 @@ import {
76
92
  currentViewDirection,
77
93
  cycleOwner,
78
94
  defaultBounds,
95
+ deltaBetween,
96
+ deltaLegs,
79
97
  directionColor,
80
98
  directionIndexOf,
81
99
  directionLabel,
100
+ distanceBetween,
82
101
  dragPlane,
83
102
  excludedFromFrame,
84
103
  featureTypeRank,
85
104
  fitDistance,
86
105
  focusForPick,
106
+ formatDegrees,
107
+ formatMillimetres,
87
108
  groupByDirection,
88
109
  hitUnderRay,
89
110
  labelGeometry,
90
111
  labelTexture,
112
+ lockToAxis,
113
+ measurementFromPoints,
114
+ measurementLabel,
115
+ measurementLabelAnchor,
116
+ midpoint,
91
117
  movedFar,
118
+ nextMeasurementId,
92
119
  normalizePartReport,
93
120
  orbitTargetOpacity,
94
121
  orthographicHalfHeight,
@@ -103,6 +130,7 @@ import {
103
130
  retargetPose,
104
131
  sameDirection,
105
132
  screenLength,
133
+ screenPoint,
106
134
  sectionAnchor,
107
135
  sectionBounds,
108
136
  sectionConstant,
@@ -110,10 +138,12 @@ import {
110
138
  sectionDepthConstant,
111
139
  sectionDepthRange,
112
140
  sectionFromPick,
141
+ sectionMeasurement,
113
142
  sectionOffset,
114
143
  sectionOptionsFromState,
115
144
  sectionPlane,
116
145
  smoothRegionNormals,
146
+ snapAt,
117
147
  squaredUp,
118
148
  startPosition,
119
149
  surfaceUnderRay,
@@ -131,21 +161,505 @@ import {
131
161
  viewUp,
132
162
  viewVector,
133
163
  visualSurfaces
134
- } from "./chunk-HWAEUJ4H.js";
164
+ } from "./chunk-4TBLMR4Z.js";
165
+
166
+ // src/hover-card.tsx
167
+ import { useEffect, useState } from "react";
168
+ import { jsx } from "react/jsx-runtime";
169
+ var HoverCard = ({ pick, children, className: className2, offset = 16 }) => {
170
+ const [pointer, setPointer] = useState(pick?.pointer);
171
+ useEffect(() => {
172
+ setPointer(pick?.pointer);
173
+ if (pick === null) return;
174
+ const follow = (event) => setPointer({ clientX: event.clientX, clientY: event.clientY });
175
+ window.addEventListener("pointermove", follow);
176
+ return () => window.removeEventListener("pointermove", follow);
177
+ }, [pick]);
178
+ if (pick === null || pointer === void 0) return null;
179
+ const style = {
180
+ position: "fixed",
181
+ left: pointer.clientX + offset,
182
+ top: pointer.clientY + offset,
183
+ pointerEvents: "none",
184
+ zIndex: 4
185
+ };
186
+ return /* @__PURE__ */ jsx(
187
+ "div",
188
+ {
189
+ className: ["viewer-hover-card", className2].filter(Boolean).join(" "),
190
+ "data-viewer-hover-card": "true",
191
+ role: "tooltip",
192
+ style,
193
+ children: children(pick)
194
+ }
195
+ );
196
+ };
197
+
198
+ // src/viewer-toolbar.tsx
199
+ import { createContext, useContext } from "react";
200
+
201
+ // src/toolbar-icons.tsx
202
+ import { jsx as jsx2 } from "react/jsx-runtime";
203
+ var SvgIcon = ({ viewBox = "0 0 24 24", fill = "none", children }) => /* @__PURE__ */ jsx2(
204
+ "svg",
205
+ {
206
+ className: "viewer-toolbar-icon",
207
+ "data-viewer-toolbar-icon": "true",
208
+ viewBox,
209
+ fill,
210
+ stroke: fill === "none" ? "currentColor" : "none",
211
+ strokeWidth: "1.5",
212
+ strokeLinecap: "round",
213
+ strokeLinejoin: "round",
214
+ "aria-hidden": "true",
215
+ children
216
+ }
217
+ );
218
+ var FitIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5M8 8h8v8H8z" }) });
219
+ var ResetIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M3 10a9 9 0 1 1 2 8M3 4v6h6" }) });
220
+ var TopIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 7l8-4 8 4-8 4zM4 7v10l8 4 8-4V7M12 11v10" }) });
221
+ var StockIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M3 6l9-4 9 4v12l-9 4-9-4zM3 6l9 4 9-4M12 10v12M7 8v8l5 2 5-2V8" }) });
222
+ var AxesIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M5 19V3m0 16h16M5 19l10-10M2 6l3-3 3 3m10 10 3 3-3 3M11 9h4v4" }) });
223
+ var GridIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M3 8l9-5 9 5-9 5zM3 8v8l9 5 9-5V8M12 13v8M7.5 5.5v8M16.5 5.5v8" }) });
224
+ var BananaIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { viewBox: "0 0 576 512", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M284.2 245.6c12.99 6.929 25.35 15.14 36.08 25.8L334.5 285.6l65.75-23.47c14.75-5.265 30.18-7.849 45.81-8.389c1.154-10.73 1.764-21.38 1.764-31.87c0-118.5-81.33-221.9-119.7-221.9c-21.01 0-40.91 17.04-40.91 39.25c0 16.18 16.74 41.9 16.74 103C303.1 170.1 300.6 203.1 284.2 245.6zM575.1 389.6c0-3.687-.8637-7.429-2.687-10.93l-15.12-29.11c-21.05-40.53-63.08-64.51-106.9-64.51c-13.43 0-27.02 2.252-40.22 6.969l-84.84 30.27l-28.59-28.41C274.4 270.9 243.7 258.1 212.8 258.1c-23.72 0-47.57 6.97-68.29 21.2L106.3 306.4c-6.732 4.631-10.35 12.07-10.35 19.63c0 14.93 12.7 23.87 24.04 23.87c4.695 0 9.443-1.376 13.61-4.26l38.13-26.23c12.43-8.525 26.71-12.69 40.91-12.69c10.64 0 21.24 2.339 30.97 6.934c-50.62 62.23-128.3 99.85-211.4 99.85C14.42 413.5 0 427.8 0 445.5v31.38c0 17.68 14.66 32.02 32.46 32.02l28.98.0009c14.15 0 34.69 1.098 59.07 1.098c93.51 0 243.4-16.15 304.8-172.4c9.021-3.22 17.95-4.712 26.53-4.712c27.65 0 51.7 15.49 63.7 38.55l15.12 29.11c3.484 6.723 11.74 12.98 21.41 12.98C564.1 413.6 575.1 403.8 575.1 389.6z" }) });
225
+ var DirectionsIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M7 3v18m-4-4 4 4 4-4M17 21V3m-4 4 4-4 4 4" }) });
226
+ var HoverIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v12H9l-5 4zm4 5h8m-8 3h5" }) });
227
+ var FocusIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v16H4zM8 8h8v8H8zM2 12h4m12 0h4M12 2v4m0 12v4" }) });
228
+ var WireframeIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 7l8-4 8 4v10l-8 4-8-4zM4 7l8 4 8-4M12 11v10M4 17l8-4 8 4M12 3v10" }) });
229
+ var SectionIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M3 6h9v12H3zM12 3v18M16 6h2m3 3v2m0 3v2m-5 3h-2" }) });
230
+ var MeasureIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M3 16L16 3l5 5L8 21zM8 11l3 3m1-7 3 3m-11 5 3 3" }) });
231
+ var ICONS = {
232
+ fit: FitIcon,
233
+ reset: ResetIcon,
234
+ top: TopIcon,
235
+ stock: StockIcon,
236
+ axes: AxesIcon,
237
+ grid: GridIcon,
238
+ banana: BananaIcon,
239
+ directions: DirectionsIcon,
240
+ hover: HoverIcon,
241
+ focus: FocusIcon,
242
+ wireframe: WireframeIcon,
243
+ section: SectionIcon,
244
+ measure: MeasureIcon
245
+ };
246
+ var ToolbarIcon = ({ name }) => {
247
+ const Icon = ICONS[name];
248
+ return /* @__PURE__ */ jsx2(Icon, {});
249
+ };
250
+
251
+ // src/viewer-toolbar.tsx
252
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
253
+ var ViewerToolbarContext = createContext(null);
254
+ var ViewerToolbarProvider = ({
255
+ controls,
256
+ children
257
+ }) => /* @__PURE__ */ jsx3(ViewerToolbarContext.Provider, { value: controls, children });
258
+ var useViewerToolbar = () => {
259
+ const controls = useContext(ViewerToolbarContext);
260
+ if (!controls) throw new Error("useViewerToolbar must be used inside <ViewerToolbarProvider>");
261
+ return controls;
262
+ };
263
+ var buttonName = (action) => `${action.slice(0, 1).toUpperCase()}${action.slice(1)}Button`;
264
+ var ToolbarButton = ({ action, icon, label, toggle = true }) => {
265
+ const control = useViewerToolbar()[action];
266
+ if (!control) {
267
+ throw new Error(`<ViewerToolbar.${buttonName(action)}> requires a '${action}' toolbar control`);
268
+ }
269
+ const pressed = control.pressed ?? false;
270
+ return /* @__PURE__ */ jsxs(
271
+ "button",
272
+ {
273
+ className: "viewer-toolbar-button",
274
+ "data-viewer-toolbar-action": action,
275
+ type: "button",
276
+ "aria-label": label(pressed),
277
+ "aria-pressed": toggle ? pressed : void 0,
278
+ onClick: control.onClick,
279
+ children: [
280
+ /* @__PURE__ */ jsx3(ToolbarIcon, { name: icon }),
281
+ /* @__PURE__ */ jsx3("span", { className: "viewer-toolbar-tooltip", "data-viewer-toolbar-tooltip": "true", role: "tooltip", children: label(pressed) })
282
+ ]
283
+ }
284
+ );
285
+ };
286
+ var StockButton = () => /* @__PURE__ */ jsx3(
287
+ ToolbarButton,
288
+ {
289
+ action: "stock",
290
+ icon: "stock",
291
+ label: (pressed) => pressed ? "Hide stock" : "Show stock"
292
+ }
293
+ );
294
+ var AxesButton = () => /* @__PURE__ */ jsx3(
295
+ ToolbarButton,
296
+ {
297
+ action: "axes",
298
+ icon: "axes",
299
+ label: (pressed) => pressed ? "Hide axis" : "Show axis"
300
+ }
301
+ );
302
+ var GridButton = () => /* @__PURE__ */ jsx3(
303
+ ToolbarButton,
304
+ {
305
+ action: "grid",
306
+ icon: "grid",
307
+ label: (pressed) => pressed ? "Hide grid" : "Show grid"
308
+ }
309
+ );
310
+ var BananaButton = () => /* @__PURE__ */ jsx3(
311
+ ToolbarButton,
312
+ {
313
+ action: "banana",
314
+ icon: "banana",
315
+ label: (pressed) => pressed ? "Banana for scale (on)" : "Banana for scale"
316
+ }
317
+ );
318
+ var DirectionsButton = () => /* @__PURE__ */ jsx3(
319
+ ToolbarButton,
320
+ {
321
+ action: "directions",
322
+ icon: "directions",
323
+ label: () => "Highlight faces by direction"
324
+ }
325
+ );
326
+ var HoverButton = () => /* @__PURE__ */ jsx3(
327
+ ToolbarButton,
328
+ {
329
+ action: "hover",
330
+ icon: "hover",
331
+ label: (pressed) => pressed ? "Disable feature hover" : "Enable feature hover"
332
+ }
333
+ );
334
+ var FocusButton = () => /* @__PURE__ */ jsx3(
335
+ ToolbarButton,
336
+ {
337
+ action: "focus",
338
+ icon: "focus",
339
+ label: (pressed) => pressed ? "Show full part" : "Focus selection"
340
+ }
341
+ );
342
+ var WireframeButton = () => /* @__PURE__ */ jsx3(ToolbarButton, { action: "wireframe", icon: "wireframe", label: () => "Wireframe" });
343
+ var SectionButton = () => /* @__PURE__ */ jsx3(
344
+ ToolbarButton,
345
+ {
346
+ action: "section",
347
+ icon: "section",
348
+ label: (pressed) => pressed ? "Exit section" : "Section"
349
+ }
350
+ );
351
+ var MeasureButton = () => /* @__PURE__ */ jsx3(
352
+ ToolbarButton,
353
+ {
354
+ action: "measure",
355
+ icon: "measure",
356
+ label: (pressed) => pressed ? "Exit measure" : "Measure"
357
+ }
358
+ );
359
+ var FitButton = () => /* @__PURE__ */ jsx3(ToolbarButton, { action: "fit", icon: "fit", label: () => "Fit", toggle: false });
360
+ var ResetButton = () => /* @__PURE__ */ jsx3(ToolbarButton, { action: "reset", icon: "reset", label: () => "Reset", toggle: false });
361
+ var TopButton = () => /* @__PURE__ */ jsx3(ToolbarButton, { action: "top", icon: "top", label: () => "Top view", toggle: false });
362
+ var Divider = () => /* @__PURE__ */ jsx3("span", { className: "viewer-toolbar-divider", "data-viewer-toolbar-divider": "true" });
363
+ var Controls = ({ children, className: className2 }) => /* @__PURE__ */ jsx3(
364
+ "div",
365
+ {
366
+ className: ["viewer-toolbar", className2].filter(Boolean).join(" "),
367
+ "data-viewer-toolbar-controls": "true",
368
+ role: "group",
369
+ "aria-label": "Viewer controls",
370
+ children
371
+ }
372
+ );
373
+ var DefaultControls = () => {
374
+ const controls = useViewerToolbar();
375
+ const display = [
376
+ controls.stock ? /* @__PURE__ */ jsx3(StockButton, {}, "stock") : null,
377
+ controls.axes ? /* @__PURE__ */ jsx3(AxesButton, {}, "axes") : null,
378
+ controls.grid ? /* @__PURE__ */ jsx3(GridButton, {}, "grid") : null,
379
+ controls.banana ? /* @__PURE__ */ jsx3(BananaButton, {}, "banana") : null
380
+ ].filter(Boolean);
381
+ const analysis = [
382
+ controls.directions ? /* @__PURE__ */ jsx3(DirectionsButton, {}, "directions") : null,
383
+ controls.hover ? /* @__PURE__ */ jsx3(HoverButton, {}, "hover") : null,
384
+ controls.focus ? /* @__PURE__ */ jsx3(FocusButton, {}, "focus") : null,
385
+ controls.wireframe ? /* @__PURE__ */ jsx3(WireframeButton, {}, "wireframe") : null,
386
+ controls.section ? /* @__PURE__ */ jsx3(SectionButton, {}, "section") : null,
387
+ controls.measure ? /* @__PURE__ */ jsx3(MeasureButton, {}, "measure") : null
388
+ ].filter(Boolean);
389
+ const camera = [
390
+ controls.fit ? /* @__PURE__ */ jsx3(FitButton, {}, "fit") : null,
391
+ controls.reset ? /* @__PURE__ */ jsx3(ResetButton, {}, "reset") : null,
392
+ controls.top ? /* @__PURE__ */ jsx3(TopButton, {}, "top") : null
393
+ ].filter(Boolean);
394
+ return /* @__PURE__ */ jsxs(Controls, { children: [
395
+ display,
396
+ display.length && analysis.length ? /* @__PURE__ */ jsx3(Divider, {}) : null,
397
+ analysis,
398
+ (display.length || analysis.length) && camera.length ? /* @__PURE__ */ jsx3(Divider, {}) : null,
399
+ camera
400
+ ] });
401
+ };
402
+ var ViewerToolbarRoot = ({ className: className2, children }) => {
403
+ useViewerToolbar();
404
+ return /* @__PURE__ */ jsx3(
405
+ "div",
406
+ {
407
+ className: ["viewer-toolbar-stack", className2].filter(Boolean).join(" "),
408
+ "data-viewer-toolbar": "true",
409
+ children: children ?? /* @__PURE__ */ jsx3(DefaultControls, {})
410
+ }
411
+ );
412
+ };
413
+ var ViewerToolbar = Object.assign(ViewerToolbarRoot, {
414
+ Controls,
415
+ Divider,
416
+ StockButton,
417
+ AxesButton,
418
+ GridButton,
419
+ BananaButton,
420
+ DirectionsButton,
421
+ HoverButton,
422
+ FocusButton,
423
+ WireframeButton,
424
+ SectionButton,
425
+ MeasureButton,
426
+ FitButton,
427
+ ResetButton,
428
+ TopButton
429
+ });
430
+
431
+ // src/stock.tsx
432
+ import { useThree } from "@react-three/fiber";
433
+ import { useEffect as useEffect2, useLayoutEffect, useMemo } from "react";
434
+ import { BoxGeometry, Vector3 as Vector32 } from "three";
435
+
436
+ // src/render/stock.ts
437
+ import {
438
+ Box3,
439
+ EdgesGeometry,
440
+ Group,
441
+ LineBasicMaterial,
442
+ LineSegments,
443
+ Mesh,
444
+ MeshLambertMaterial,
445
+ Vector3
446
+ } from "three";
447
+ function fixedBoxStockBounds(geometry, dimensions, position = "model_centered", positionOffset = 0, offset = { x: 0, y: 0, z: 0 }) {
448
+ if (![dimensions.x, dimensions.y, dimensions.z].every(Number.isFinite) || [dimensions.x, dimensions.y, dimensions.z].some((value) => value <= 0)) {
449
+ throw new RangeError("Fixed stock dimensions must be finite and positive.");
450
+ }
451
+ if (!Number.isFinite(positionOffset)) {
452
+ throw new RangeError("Fixed stock position offset must be finite.");
453
+ }
454
+ if (![offset.x, offset.y, offset.z].every(Number.isFinite)) {
455
+ throw new RangeError("Fixed stock offset must be finite.");
456
+ }
457
+ const part = partBounds(geometry);
458
+ const center = part.getCenter(new Vector3());
459
+ const z = position === "offset_from_top" ? part.max.z + positionOffset - dimensions.z / 2 : position === "offset_from_bottom" ? part.min.z - positionOffset + dimensions.z / 2 : center.z;
460
+ const half = new Vector3(dimensions.x / 2, dimensions.y / 2, dimensions.z / 2);
461
+ return new Box3(
462
+ new Vector3(center.x, center.y, z).sub(half),
463
+ new Vector3(center.x, center.y, z).add(half)
464
+ ).translate(new Vector3(offset.x, offset.y, offset.z));
465
+ }
466
+ function boxStockBounds(geometry, allowance = 0, offset = { x: 0, y: 0, z: 0 }) {
467
+ const padding = typeof allowance === "number" ? new Vector3(allowance, allowance, allowance) : "wall" in allowance ? new Vector3(allowance.wall, allowance.wall, allowance.floor) : new Vector3(allowance.x, allowance.y, allowance.z);
468
+ if (padding.toArray().some((value) => !Number.isFinite(value) || value < 0)) {
469
+ throw new RangeError("Stock allowance must be finite and non-negative.");
470
+ }
471
+ if (![offset.x, offset.y, offset.z].every(Number.isFinite)) {
472
+ throw new RangeError("Stock offset must be finite.");
473
+ }
474
+ const box = partBounds(geometry);
475
+ box.expandByVector(padding).translate(new Vector3(offset.x, offset.y, offset.z));
476
+ const size = box.getSize(new Vector3());
477
+ if (size.toArray().some((value) => !Number.isFinite(value) || value <= 0)) {
478
+ throw new RangeError("Stock dimensions must be finite and positive.");
479
+ }
480
+ return box;
481
+ }
482
+ function partBounds(geometry) {
483
+ const position = geometry.getAttribute("position");
484
+ if (!position || position.count === 0)
485
+ throw new RangeError("Stock needs non-empty part geometry.");
486
+ const box = new Box3();
487
+ const point = new Vector3();
488
+ for (let index = 0; index < position.count; index += 1) {
489
+ point.fromBufferAttribute(position, index);
490
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y) || !Number.isFinite(point.z)) {
491
+ throw new RangeError("Stock needs finite part coordinates.");
492
+ }
493
+ box.expandByPoint(point);
494
+ }
495
+ return box;
496
+ }
497
+ function createStock(geometry) {
498
+ const material = new MeshLambertMaterial({ transparent: true, depthWrite: false });
499
+ const edgeMaterial = new LineBasicMaterial({ transparent: true, depthWrite: false });
500
+ const mesh = new Mesh(geometry, material);
501
+ const edgeGeometry = new EdgesGeometry(geometry, 15);
502
+ const edges = new LineSegments(edgeGeometry, edgeMaterial);
503
+ const object = new Group();
504
+ object.userData[STOCK_OBJECT] = true;
505
+ mesh.renderOrder = 5;
506
+ edges.renderOrder = 6;
507
+ mesh.raycast = () => {
508
+ };
509
+ edges.raycast = () => {
510
+ };
511
+ object.add(mesh, edges);
512
+ return {
513
+ object,
514
+ material,
515
+ edgeMaterial,
516
+ edges,
517
+ dispose() {
518
+ material.dispose();
519
+ edgeMaterial.dispose();
520
+ edgeGeometry.dispose();
521
+ }
522
+ };
523
+ }
524
+
525
+ // src/stock.tsx
526
+ import { jsx as jsx4 } from "react/jsx-runtime";
527
+ var Stock = ({
528
+ geometry,
529
+ color = 12176354,
530
+ opacity = 0.2,
531
+ edgeColor = 11058648,
532
+ edgeOpacity = 0.75,
533
+ showEdges = true
534
+ }) => {
535
+ const invalidate = useThree((state) => state.invalidate);
536
+ const stock = useMemo(() => createStock(geometry), [geometry]);
537
+ useEffect2(() => () => stock.dispose(), [stock]);
538
+ useLayoutEffect(() => {
539
+ stock.material.color.setHex(color);
540
+ stock.material.opacity = opacity;
541
+ stock.edgeMaterial.color.setHex(edgeColor);
542
+ stock.edgeMaterial.opacity = edgeOpacity;
543
+ stock.edges.visible = showEdges;
544
+ invalidate();
545
+ }, [color, edgeColor, edgeOpacity, invalidate, opacity, showEdges, stock]);
546
+ return /* @__PURE__ */ jsx4("primitive", { object: stock.object, dispose: null });
547
+ };
548
+ var BoxStock = ({
549
+ partGeometry,
550
+ dimensions,
551
+ position = "model_centered",
552
+ positionOffset = 0,
553
+ allowance = 0,
554
+ offset,
555
+ ...props
556
+ }) => {
557
+ const ox = offset?.x ?? 0;
558
+ const oy = offset?.y ?? 0;
559
+ const oz = offset?.z ?? 0;
560
+ const geometry = useMemo(() => {
561
+ const box = dimensions ? fixedBoxStockBounds(partGeometry, dimensions, position, positionOffset, {
562
+ x: ox,
563
+ y: oy,
564
+ z: oz
565
+ }) : boxStockBounds(partGeometry, allowance, { x: ox, y: oy, z: oz });
566
+ const size = box.getSize(new Vector32());
567
+ const center = box.getCenter(new Vector32());
568
+ return new BoxGeometry(size.x, size.y, size.z).translate(center.x, center.y, center.z);
569
+ }, [allowance, dimensions, ox, oy, oz, partGeometry, position, positionOffset]);
570
+ useEffect2(() => () => geometry.dispose(), [geometry]);
571
+ return /* @__PURE__ */ jsx4(Stock, { geometry, ...props });
572
+ };
573
+
574
+ // src/banana.tsx
575
+ import { useGLTF } from "@react-three/drei";
576
+ import { useThree as useThree2 } from "@react-three/fiber";
577
+ import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef } from "react";
578
+
579
+ // src/render/banana.ts
580
+ import { Vector3 as Vector33 } from "three";
581
+ var BANANA_GAP = 0.1;
582
+ function bananaPosition(part, banana) {
583
+ const size = part.getSize(new Vector33());
584
+ const reach = Math.max(size.x, size.y, size.z);
585
+ const centre = part.getCenter(new Vector33());
586
+ return new Vector33(
587
+ part.max.x + reach * BANANA_GAP - banana.min.x,
588
+ centre.y - (banana.min.y + banana.max.y) / 2,
589
+ part.min.z - banana.min.z
590
+ );
591
+ }
592
+ function bananaFrameBounds(part, banana, position) {
593
+ return part.clone().union(banana.clone().translate(position));
594
+ }
595
+
596
+ // src/banana.tsx
597
+ import { jsx as jsx5 } from "react/jsx-runtime";
598
+ var BANANA_MODEL_URL = new URL("./assets/banana.glb", import.meta.url).href;
599
+ var SKIN = "#e8b530";
600
+ var FURNITURE = { [EXCLUDE_FROM_FRAME]: true };
601
+ var Banana = ({ onPlaced }) => {
602
+ const part = useContentBox();
603
+ const invalidate = useThree2((state) => state.invalidate);
604
+ const gltf = useGLTF(BANANA_MODEL_URL);
605
+ const group = useRef(null);
606
+ const geometry = useMemo2(() => {
607
+ let found = null;
608
+ gltf.scene.traverse((object) => {
609
+ const mesh = object;
610
+ if (found === null && mesh.isMesh) found = mesh.geometry;
611
+ });
612
+ const source = found;
613
+ if (!source) return null;
614
+ const own = source.clone();
615
+ own.computeVertexNormals();
616
+ own.rotateY(Math.PI / 2);
617
+ own.rotateX(Math.PI / 2);
618
+ own.computeBoundingBox();
619
+ return own;
620
+ }, [gltf]);
621
+ useLayoutEffect2(() => {
622
+ const node = group.current;
623
+ const own = geometry?.boundingBox;
624
+ if (!node || !own || part.isEmpty()) return;
625
+ const position = bananaPosition(part, own);
626
+ node.position.copy(position);
627
+ node.visible = true;
628
+ node.updateWorldMatrix(true, true);
629
+ onPlaced?.(bananaFrameBounds(part, own, position));
630
+ invalidate();
631
+ }, [geometry, invalidate, onPlaced, part]);
632
+ if (!geometry) return null;
633
+ return /* @__PURE__ */ jsx5("group", { ref: group, userData: FURNITURE, visible: false, children: /* @__PURE__ */ jsx5("mesh", { geometry, children: /* @__PURE__ */ jsx5("meshStandardMaterial", { color: SKIN, roughness: 0.85, metalness: 0 }) }) });
634
+ };
635
+
636
+ // src/render/direction-highlights.ts
637
+ function directionHighlights(model, activeDirection = null) {
638
+ const features = model.features.map((feature) => ({ feature, index: directionIndexOf(model, feature.machiningDirection) })).filter(({ index }) => index >= 0 && (activeDirection === null || index === activeDirection)).sort(
639
+ (a, b) => featureTypeRank(a.feature.featureType) - featureTypeRank(b.feature.featureType) || a.index - b.index || (a.feature.tag < b.feature.tag ? -1 : a.feature.tag > b.feature.tag ? 1 : 0)
640
+ );
641
+ const colors = /* @__PURE__ */ new Map();
642
+ for (const { feature, index } of features) {
643
+ for (const region of model.regionIndex.regionsForFeature(feature.tag)) {
644
+ if (!colors.has(region)) colors.set(region, { region, color: directionColor(index) });
645
+ }
646
+ }
647
+ return [...colors.values()].sort((a, b) => a.region - b.region);
648
+ }
135
649
 
136
650
  // src/primitives.tsx
137
651
  import { GizmoHelper } from "@react-three/drei";
138
- import { useThree } from "@react-three/fiber";
139
- import { useEffect, useMemo, useRef, useState } from "react";
652
+ import { useThree as useThree3 } from "@react-three/fiber";
653
+ import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
140
654
 
141
655
  // src/render/grid.ts
142
- import { BufferGeometry, Float32BufferAttribute, Vector3 } from "three";
656
+ import { BufferGeometry, Float32BufferAttribute, Vector3 as Vector34 } from "three";
143
657
  var STEPS = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1e3];
144
658
  var TARGET_CELLS = 10;
145
659
  var OVERHANG = 1.6;
146
660
  function gridSpec(box) {
147
- const size = box.getSize(new Vector3());
148
- const center = box.getCenter(new Vector3());
661
+ const size = box.getSize(new Vector34());
662
+ const center = box.getCenter(new Vector34());
149
663
  const largest = Math.max(size.x, size.y, 1e-6);
150
664
  const ideal = largest / TARGET_CELLS;
151
665
  const step = [...STEPS].reverse().find((candidate) => candidate <= ideal) ?? STEPS[0];
@@ -189,26 +703,26 @@ function gridFor(box, options = {}) {
189
703
  }
190
704
 
191
705
  // src/primitives.tsx
192
- import { jsx, jsxs } from "react/jsx-runtime";
193
- var FURNITURE = { [EXCLUDE_FROM_FRAME]: true };
706
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
707
+ var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
194
708
  var Grid = ({ step, extent, color, opacity = 0.35 }) => {
195
709
  const theme = resolveTheme();
196
710
  const box = useContentBox();
197
- const geometry = useMemo(() => gridFor(box, { step, extent }), [box, extent, step]);
198
- useEffect(() => () => geometry?.dispose(), [geometry]);
711
+ const geometry = useMemo3(() => gridFor(box, { step, extent }), [box, extent, step]);
712
+ useEffect3(() => () => geometry?.dispose(), [geometry]);
199
713
  if (!geometry) return null;
200
- return /* @__PURE__ */ jsx("lineSegments", { geometry, renderOrder: -1, raycast: () => null, userData: FURNITURE, children: /* @__PURE__ */ jsx("lineBasicMaterial", { color: color ?? theme.cubeEdge, transparent: true, opacity }) });
714
+ return /* @__PURE__ */ jsx6("lineSegments", { geometry, renderOrder: -1, raycast: () => null, userData: FURNITURE2, children: /* @__PURE__ */ jsx6("lineBasicMaterial", { color: color ?? theme.cubeEdge, transparent: true, opacity }) });
201
715
  };
202
- var Axes = ({ size = 25 }) => /* @__PURE__ */ jsx("axesHelper", { args: [size], userData: FURNITURE, raycast: () => null });
716
+ var Axes = ({ size = 25 }) => /* @__PURE__ */ jsx6("axesHelper", { args: [size], userData: FURNITURE2, raycast: () => null });
203
717
  var ViewCube = ({
204
718
  alignment = "top-right",
205
719
  margin = [80, 80],
206
720
  theme,
207
721
  onViewChange
208
722
  }) => {
209
- const resolved = useMemo(() => resolveTheme(theme), [theme]);
210
- const zones = useMemo(() => cubeZones(), []);
211
- return /* @__PURE__ */ jsx(GizmoHelper, { alignment, margin, children: /* @__PURE__ */ jsx(CubePanels, { zones, theme: resolved, onViewChange }) });
723
+ const resolved = useMemo3(() => resolveTheme(theme), [theme]);
724
+ const zones = useMemo3(() => cubeZones(), []);
725
+ return /* @__PURE__ */ jsx6(GizmoHelper, { alignment, margin, children: /* @__PURE__ */ jsx6(CubePanels, { zones, theme: resolved, onViewChange }) });
212
726
  };
213
727
  var CubePanels = ({
214
728
  zones,
@@ -216,15 +730,15 @@ var CubePanels = ({
216
730
  onViewChange
217
731
  }) => {
218
732
  const controls = useViewerControls();
219
- const invalidate = useThree((state) => state.invalidate);
220
- const groupRef = useRef(null);
221
- const [hovered, setHovered] = useState(null);
222
- const panels = useMemo(
733
+ const invalidate = useThree3((state) => state.invalidate);
734
+ const groupRef = useRef2(null);
735
+ const [hovered, setHovered] = useState2(null);
736
+ const panels = useMemo3(
223
737
  () => zones.map((zone) => ({ zone, geometry: panelGeometry(zone) })),
224
738
  [zones]
225
739
  );
226
- const outline = useMemo(() => cubeOutlineGeometry(zones), [zones]);
227
- const labels = useMemo(
740
+ const outline = useMemo3(() => cubeOutlineGeometry(zones), [zones]);
741
+ const labels = useMemo3(
228
742
  () => zones.filter((zone) => zone.kind === "face").map((zone) => ({
229
743
  zone,
230
744
  geometry: labelGeometry(zone),
@@ -232,7 +746,7 @@ var CubePanels = ({
232
746
  })),
233
747
  [theme.cubeLabel, zones]
234
748
  );
235
- useEffect(
749
+ useEffect3(
236
750
  () => () => {
237
751
  for (const panel of panels) panel.geometry.dispose();
238
752
  for (const label of labels) {
@@ -251,8 +765,8 @@ var CubePanels = ({
251
765
  setHovered(name);
252
766
  invalidate();
253
767
  };
254
- return /* @__PURE__ */ jsxs("group", { ref: groupRef, scale: 38, children: [
255
- panels.map(({ zone, geometry }) => /* @__PURE__ */ jsx(
768
+ return /* @__PURE__ */ jsxs2("group", { ref: groupRef, scale: 38, children: [
769
+ panels.map(({ zone, geometry }) => /* @__PURE__ */ jsx6(
256
770
  "mesh",
257
771
  {
258
772
  geometry,
@@ -265,7 +779,7 @@ var CubePanels = ({
265
779
  event.stopPropagation();
266
780
  choose(zone);
267
781
  },
268
- children: /* @__PURE__ */ jsx(
782
+ children: /* @__PURE__ */ jsx6(
269
783
  "meshLambertMaterial",
270
784
  {
271
785
  color: hovered === zone.name ? theme.hover : theme.cube,
@@ -277,19 +791,19 @@ var CubePanels = ({
277
791
  },
278
792
  zone.name
279
793
  )),
280
- labels.map(({ zone, geometry, texture }) => /* @__PURE__ */ jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsx("meshBasicMaterial", { map: texture, transparent: true }) }, `${zone.name}-label`)),
281
- /* @__PURE__ */ jsx("lineSegments", { geometry: outline, raycast: () => null, children: /* @__PURE__ */ jsx("lineBasicMaterial", { color: theme.cubeEdge }) }),
282
- /* @__PURE__ */ jsx("ambientLight", { intensity: 1.4 }),
283
- /* @__PURE__ */ jsx("directionalLight", { position: [viewVector("top-front-right").x, 1, 2], intensity: 1.1 })
794
+ labels.map(({ zone, geometry, texture }) => /* @__PURE__ */ jsx6("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsx6("meshBasicMaterial", { map: texture, transparent: true }) }, `${zone.name}-label`)),
795
+ /* @__PURE__ */ jsx6("lineSegments", { geometry: outline, raycast: () => null, children: /* @__PURE__ */ jsx6("lineBasicMaterial", { color: theme.cubeEdge }) }),
796
+ /* @__PURE__ */ jsx6("ambientLight", { intensity: 1.4 }),
797
+ /* @__PURE__ */ jsx6("directionalLight", { position: [viewVector("top-front-right").x, 1, 2], intensity: 1.1 })
284
798
  ] });
285
799
  };
286
800
 
287
801
  // src/direction-arrows.tsx
288
- import { useThree as useThree2 } from "@react-three/fiber";
289
- import { useMemo as useMemo2 } from "react";
290
- import { Quaternion, Vector3 as Vector32 } from "three";
291
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
292
- var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
802
+ import { useThree as useThree4 } from "@react-three/fiber";
803
+ import { useMemo as useMemo4 } from "react";
804
+ import { Quaternion, Vector3 as Vector35 } from "three";
805
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
806
+ var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
293
807
  var DirectionArrows = ({
294
808
  directions,
295
809
  activeDirection = null,
@@ -301,7 +815,7 @@ var DirectionArrows = ({
301
815
  visible = true
302
816
  }) => {
303
817
  const box = useContentBox();
304
- const resolved = useMemo2(() => resolveTheme(theme), [theme]);
818
+ const resolved = useMemo4(() => resolveTheme(theme), [theme]);
305
819
  const shown = shownDirection === void 0 ? activeDirection : shownDirection;
306
820
  const drawn = (index) => {
307
821
  if (shown === null) return true;
@@ -313,10 +827,10 @@ var DirectionArrows = ({
313
827
  // Excluded from framing: the arrows are deliberately placed outside the
314
828
  // part, so a Fit that measured them would frame the arrows and leave the
315
829
  // part small in the middle of them.
316
- /* @__PURE__ */ jsxs2("group", { userData: FURNITURE2, children: [
830
+ /* @__PURE__ */ jsxs3("group", { userData: FURNITURE3, children: [
317
831
  directions.map((direction, index) => {
318
832
  if (!drawn(index)) return null;
319
- return /* @__PURE__ */ jsx2(
833
+ return /* @__PURE__ */ jsx7(
320
834
  Arrow,
321
835
  {
322
836
  direction,
@@ -328,7 +842,7 @@ var DirectionArrows = ({
328
842
  `candidate-${index}`
329
843
  );
330
844
  }),
331
- namedDirections.map((named) => /* @__PURE__ */ jsx2(
845
+ namedDirections.map((named) => /* @__PURE__ */ jsx7(
332
846
  Arrow,
333
847
  {
334
848
  direction: named.direction,
@@ -339,17 +853,17 @@ var DirectionArrows = ({
339
853
  },
340
854
  `named-${named.index}`
341
855
  )),
342
- previewDirection ? /* @__PURE__ */ jsx2(Arrow, { direction: previewDirection, box, color: resolved.hover, opacity: 0.95, onTop: true }) : null
856
+ previewDirection ? /* @__PURE__ */ jsx7(Arrow, { direction: previewDirection, box, color: resolved.hover, opacity: 0.95, onTop: true }) : null
343
857
  ] })
344
858
  );
345
859
  };
346
860
  var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
347
- const invalidate = useThree2((state) => state.invalidate);
348
- const { tip, length, quaternion } = useMemo2(() => {
861
+ const invalidate = useThree4((state) => state.invalidate);
862
+ const { tip, length, quaternion } = useMemo4(() => {
349
863
  const placement = arrowPlacement(direction, box);
350
864
  const aim = new Quaternion().setFromUnitVectors(
351
865
  CONE_AXIS,
352
- new Vector32(direction.x, direction.y, direction.z).normalize()
866
+ new Vector35(direction.x, direction.y, direction.z).normalize()
353
867
  );
354
868
  return { tip: placement.tip, length: placement.length, quaternion: aim };
355
869
  }, [box, direction]);
@@ -362,234 +876,32 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
362
876
  invalidate();
363
877
  }
364
878
  } : {};
365
- return /* @__PURE__ */ jsxs2("group", { position: tip, quaternion, renderOrder: onTop ? 10 : 3, children: [
366
- /* @__PURE__ */ jsxs2("mesh", { position: [0, head * 0.5, 0], rotation: [Math.PI, 0, 0], ...press, children: [
367
- /* @__PURE__ */ jsx2("coneGeometry", { args: [length * HEAD_RADIUS, head, 20] }),
368
- /* @__PURE__ */ jsx2("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
879
+ return /* @__PURE__ */ jsxs3("group", { position: tip, quaternion, renderOrder: onTop ? 10 : 3, children: [
880
+ /* @__PURE__ */ jsxs3("mesh", { position: [0, head * 0.5, 0], rotation: [Math.PI, 0, 0], ...press, children: [
881
+ /* @__PURE__ */ jsx7("coneGeometry", { args: [length * HEAD_RADIUS, head, 20] }),
882
+ /* @__PURE__ */ jsx7("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
369
883
  ] }),
370
- /* @__PURE__ */ jsxs2("mesh", { position: [0, head + shaft * 0.5, 0], ...press, children: [
371
- /* @__PURE__ */ jsx2("cylinderGeometry", { args: [length * SHAFT_RADIUS, length * SHAFT_RADIUS, shaft, 12] }),
372
- /* @__PURE__ */ jsx2("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
884
+ /* @__PURE__ */ jsxs3("mesh", { position: [0, head + shaft * 0.5, 0], ...press, children: [
885
+ /* @__PURE__ */ jsx7("cylinderGeometry", { args: [length * SHAFT_RADIUS, length * SHAFT_RADIUS, shaft, 12] }),
886
+ /* @__PURE__ */ jsx7("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
373
887
  ] })
374
888
  ] });
375
889
  };
376
890
 
377
891
  // src/section-tool.tsx
378
- import { useFrame, useThree as useThree3 } from "@react-three/fiber";
379
- import { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
892
+ import { useFrame, useThree as useThree5 } from "@react-three/fiber";
893
+ import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as useState3, useSyncExternalStore } from "react";
380
894
  import {
381
895
  BufferGeometry as BufferGeometry2,
382
896
  DoubleSide,
383
897
  Quaternion as Quaternion2,
384
898
  Raycaster,
385
- Vector2 as Vector22,
386
- Vector3 as Vector34
899
+ Vector2,
900
+ Vector3 as Vector36
387
901
  } from "three";
388
-
389
- // src/render/measure.ts
390
- import { Vector2, Vector3 as Vector33 } from "three";
391
- var MEASURE_RENDER_ORDER = 7;
392
- var AXIS_COLORS = { x: 16739179, y: 7331978, z: 7314431 };
393
- var SNAP_PIXELS = {
394
- vertex: 14,
395
- midpoint: 12,
396
- edge: 9
397
- };
398
- var SNAP_MARKER_PIXELS = {
399
- vertex: 6,
400
- midpoint: 5,
401
- edge: 4,
402
- face: 3.5
403
- };
404
- var MEASURE_MARKER_PIXELS = 3.5;
405
- var MEASURE_LINE_PIXELS = {
406
- line: 2,
407
- leg: 1.5,
408
- edge: 3
409
- };
410
- var DELTA_DASH_PIXELS = 6;
411
- var ANGLE_ARC_FRACTION = 0.3;
412
- var ANGLE_ARC_SEGMENTS = 24;
413
- function snapAt(hit, edges, camera, viewport) {
414
- const face = {
415
- kind: "face",
416
- point: hit.point.clone(),
417
- normal: hit.normal.clone(),
418
- edge: null
419
- };
420
- if (!edges) return face;
421
- const positions = edges.positions;
422
- const segments = Math.floor(positions.length / 6);
423
- if (segments === 0) return face;
424
- const inverse = edges.matrixWorld.clone().invert();
425
- const p = hit.point.clone().applyMatrix4(inverse);
426
- const n = hit.normal.clone().transformDirection(inverse);
427
- const reach = screenLength(camera, hit.point, viewport, SNAP_PIXELS.vertex) * 1.5;
428
- const reachSq = reach * reach;
429
- const pointer = screenPoint(camera, hit.point, viewport);
430
- const best = {
431
- vertex: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 },
432
- midpoint: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 },
433
- edge: { distance: Number.POSITIVE_INFINITY, index: -1, t: 0 }
434
- };
435
- const a = new Vector33();
436
- const b = new Vector33();
437
- const ab = new Vector33();
438
- const ap = new Vector33();
439
- const q = new Vector33();
440
- const pixel = new Vector2();
441
- const consider = (kind, point, index, t) => {
442
- const distance = screenPoint(
443
- camera,
444
- q.copy(point).applyMatrix4(edges.matrixWorld),
445
- viewport,
446
- pixel
447
- ).distanceTo(pointer);
448
- if (distance <= SNAP_PIXELS[kind] && distance < best[kind].distance) {
449
- best[kind] = { distance, index, t };
450
- }
451
- };
452
- for (let index = 0; index < segments; index += 1) {
453
- const offset = index * 6;
454
- a.set(positions[offset], positions[offset + 1], positions[offset + 2]);
455
- b.set(positions[offset + 3], positions[offset + 4], positions[offset + 5]);
456
- if (Math.abs(ap.subVectors(a, p).dot(n)) > reach) continue;
457
- if (Math.abs(ap.subVectors(b, p).dot(n)) > reach) continue;
458
- ab.subVectors(b, a);
459
- ap.subVectors(p, a);
460
- const lengthSq = ab.lengthSq();
461
- const t = lengthSq === 0 ? 0 : Math.min(1, Math.max(0, ap.dot(ab) / lengthSq));
462
- q.copy(a).addScaledVector(ab, t);
463
- if (q.distanceToSquared(p) > reachSq) continue;
464
- consider("vertex", a, index, 0);
465
- consider("vertex", b, index, 1);
466
- consider("midpoint", q.copy(a).addScaledVector(ab, 0.5), index, 0.5);
467
- consider("edge", q.copy(a).addScaledVector(ab, t), index, t);
468
- }
469
- for (const kind of ["vertex", "midpoint", "edge"]) {
470
- const found = best[kind];
471
- if (found.index < 0) continue;
472
- const offset = found.index * 6;
473
- const start = new Vector33(positions[offset], positions[offset + 1], positions[offset + 2]);
474
- const end = new Vector33(positions[offset + 3], positions[offset + 4], positions[offset + 5]);
475
- const point = start.clone().lerp(end, found.t);
476
- start.applyMatrix4(edges.matrixWorld);
477
- end.applyMatrix4(edges.matrixWorld);
478
- point.applyMatrix4(edges.matrixWorld);
479
- return {
480
- kind,
481
- point,
482
- normal: hit.normal.clone(),
483
- edge: kind === "vertex" ? null : [start, end]
484
- };
485
- }
486
- return face;
487
- }
488
- function screenPoint(camera, point, viewport, into = new Vector2()) {
489
- const ndc = point.clone().project(camera);
490
- return into.set((ndc.x + 1) / 2 * viewport.width, (1 - ndc.y) / 2 * viewport.height);
491
- }
492
- var POINTS_PER_MEASUREMENT = {
493
- distance: 2,
494
- angle: 3
495
- };
496
- function measurementFromPoints(kind, points, id) {
497
- if (points.length < POINTS_PER_MEASUREMENT[kind]) return null;
498
- const [a, b, c] = points;
499
- if (kind === "distance") return { id, kind, points: [copy(a), copy(b)] };
500
- return { id, kind, points: [copy(a), copy(b), copy(c)] };
501
- }
502
- function nextMeasurementId(held) {
503
- return held.reduce((top, entry) => Math.max(top, entry.id), 0) + 1;
504
- }
505
- function distanceBetween(a, b) {
506
- return Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
507
- }
508
- function deltaBetween(a, b) {
509
- return { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z };
510
- }
511
- function midpoint(a, b) {
512
- return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
513
- }
514
- function angleAt(vertex, a, b) {
515
- const u = new Vector33(a.x - vertex.x, a.y - vertex.y, a.z - vertex.z);
516
- const w = new Vector33(b.x - vertex.x, b.y - vertex.y, b.z - vertex.z);
517
- if (u.lengthSq() === 0 || w.lengthSq() === 0) return 0;
518
- return u.angleTo(w) * 180 / Math.PI;
519
- }
520
- function lockToAxis(from, to) {
521
- const delta = deltaBetween(from, to);
522
- let axis = "x";
523
- for (const candidate of ["y", "z"]) {
524
- if (Math.abs(delta[candidate]) > Math.abs(delta[axis])) axis = candidate;
525
- }
526
- return { axis, point: { ...from, [axis]: to[axis] } };
527
- }
528
- function deltaLegs(a, b) {
529
- const delta = deltaBetween(a, b);
530
- const threshold = Math.max(1e-9, distanceBetween(a, b) * 1e-6);
531
- const legs = [];
532
- let cursor = copy(a);
533
- for (const axis of ["x", "y", "z"]) {
534
- if (Math.abs(delta[axis]) <= threshold) continue;
535
- const to = { ...cursor, [axis]: b[axis] };
536
- legs.push({ axis, from: cursor, to, length: Math.abs(delta[axis]) });
537
- cursor = to;
538
- }
539
- return legs.length > 1 ? legs : [];
540
- }
541
- function angleArc(vertex, a, b, radius, segments = ANGLE_ARC_SEGMENTS) {
542
- const v = new Vector33(vertex.x, vertex.y, vertex.z);
543
- const u = new Vector33(a.x, a.y, a.z).sub(v);
544
- const w = new Vector33(b.x, b.y, b.z).sub(v);
545
- if (u.lengthSq() === 0 || w.lengthSq() === 0) return [];
546
- u.normalize();
547
- w.normalize();
548
- const theta = u.angleTo(w);
549
- const sine = Math.sin(theta);
550
- if (sine < 1e-6) {
551
- return [v.clone().addScaledVector(u, radius), v.clone().addScaledVector(w, radius)];
552
- }
553
- const points = [];
554
- for (let i = 0; i <= segments; i += 1) {
555
- const t = i / segments;
556
- const direction = u.clone().multiplyScalar(Math.sin((1 - t) * theta) / sine).addScaledVector(w, Math.sin(t * theta) / sine);
557
- points.push(v.clone().addScaledVector(direction, radius));
558
- }
559
- return points;
560
- }
561
- function angleArcRadius(vertex, a, b) {
562
- return Math.min(distanceBetween(vertex, a), distanceBetween(vertex, b)) * ANGLE_ARC_FRACTION;
563
- }
564
- function formatMillimetres(mm, digits = 2) {
565
- return `${mm.toFixed(digits)} mm`;
566
- }
567
- function formatDegrees(degrees, digits = 1) {
568
- return `${degrees.toFixed(digits)}\xB0`;
569
- }
570
- function measurementLabel(measurement, format = formatMillimetres) {
571
- if (measurement.kind === "distance") {
572
- const [a2, b2] = measurement.points;
573
- return format(distanceBetween(a2, b2));
574
- }
575
- const [a, vertex, b] = measurement.points;
576
- return formatDegrees(angleAt(vertex, a, b));
577
- }
578
- function measurementLabelAnchor(measurement) {
579
- if (measurement.kind === "distance") return midpoint(measurement.points[0], measurement.points[1]);
580
- const [a, vertex, b] = measurement.points;
581
- const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b) * 1.6, 2);
582
- const middle = arc[1] ?? arc[0];
583
- return middle ? { x: middle.x, y: middle.y, z: middle.z } : copy(vertex);
584
- }
585
- function copy(v) {
586
- return { x: v.x, y: v.y, z: v.z };
587
- }
588
-
589
- // src/section-tool.tsx
590
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
591
- var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
592
- var Z = new Vector34(0, 0, 1);
902
+ import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
903
+ var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
904
+ var Z = new Vector36(0, 0, 1);
593
905
  var AXIS_ROTATIONS = {
594
906
  x: [0, Math.PI / 2, 0],
595
907
  y: [Math.PI / 2, 0, 0],
@@ -601,12 +913,12 @@ var SectionTool = ({ theme }) => {
601
913
  const store = useSectionStore();
602
914
  const section = useSyncExternalStore(store.subscribe, store.get, store.get);
603
915
  const box = useContentBox();
604
- const resolved = useMemo3(() => resolveTheme(theme), [theme]);
605
- useEffect2(() => {
916
+ const resolved = useMemo5(() => resolveTheme(theme), [theme]);
917
+ useEffect4(() => {
606
918
  store.setEngaged(true);
607
919
  return () => store.setEngaged(false);
608
920
  }, [store]);
609
- useEffect2(() => {
921
+ useEffect4(() => {
610
922
  if (!section) return void 0;
611
923
  const clear = (event) => {
612
924
  if (event.key === "Escape") store.set(null);
@@ -615,39 +927,43 @@ var SectionTool = ({ theme }) => {
615
927
  return () => window.removeEventListener("keydown", clear);
616
928
  }, [section, store]);
617
929
  if (box.isEmpty()) return null;
618
- return /* @__PURE__ */ jsx3("group", { userData: FURNITURE3, children: section ? /* @__PURE__ */ jsx3(CutSheet, { box, section, theme: resolved }) : /* @__PURE__ */ jsx3(Picker, { box, theme: resolved }) });
930
+ return /* @__PURE__ */ jsx8("group", { userData: FURNITURE4, children: section ? /* @__PURE__ */ jsx8(CutSheet, { box, section, theme: resolved }) : /* @__PURE__ */ jsx8(Picker, { box, theme: resolved }) });
619
931
  };
620
932
  var useSquare = () => {
621
- const square = useMemo3(
933
+ const square = useMemo5(
622
934
  () => new BufferGeometry2().setFromPoints([
623
- new Vector34(-1, 1, 0),
624
- new Vector34(1, 1, 0),
625
- new Vector34(1, -1, 0),
626
- new Vector34(-1, -1, 0)
935
+ new Vector36(-1, 1, 0),
936
+ new Vector36(1, 1, 0),
937
+ new Vector36(1, -1, 0),
938
+ new Vector36(-1, -1, 0)
627
939
  ]),
628
940
  []
629
941
  );
630
- useEffect2(() => () => square.dispose(), [square]);
942
+ useEffect4(() => () => square.dispose(), [square]);
631
943
  return square;
632
944
  };
633
945
  var Picker = ({ box, theme }) => {
634
946
  const store = useSectionStore();
635
- const camera = useThree3((state) => state.camera);
636
- const scene = useThree3((state) => state.scene);
637
- const domElement = useThree3((state) => state.gl.domElement);
638
- const invalidate = useThree3((state) => state.invalidate);
947
+ const camera = useThree5((state) => state.camera);
948
+ const scene = useThree5((state) => state.scene);
949
+ const domElement = useThree5((state) => state.gl.domElement);
950
+ const invalidate = useThree5((state) => state.invalidate);
639
951
  const isTap = useTapGuard();
640
952
  const square = useSquare();
641
- const previewRef = useRef2(null);
642
- const hover = useRef2(null);
643
- const [hoveredPlane, setHoveredPlane] = useState2(null);
644
- const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
645
- const centre = useMemo3(() => box.getCenter(new Vector34()), [box]);
953
+ const previewRef = useRef3(null);
954
+ const hover = useRef3(null);
955
+ const [hoveredPlane, setHoveredPlane] = useState3(null);
956
+ const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
957
+ const centre = useMemo5(() => box.getCenter(new Vector36()), [box]);
646
958
  const previewSize = span * PREVIEW_SCALE;
647
- useEffect2(() => {
959
+ useEffect4(() => {
960
+ store.setPicking(true);
961
+ return () => store.setPicking(false);
962
+ }, [store]);
963
+ useEffect4(() => {
648
964
  const raycaster = new Raycaster();
649
- const pointer = new Vector22();
650
- const aim = new Vector34();
965
+ const pointer = new Vector2();
966
+ const aim = new Vector36();
651
967
  const hide = () => {
652
968
  if (!hover.current) return;
653
969
  hover.current = null;
@@ -674,6 +990,7 @@ var Picker = ({ box, theme }) => {
674
990
  invalidate();
675
991
  };
676
992
  const up = (event) => {
993
+ move(event);
677
994
  const hit = hover.current;
678
995
  if (event.button !== 0 || !hit || !isTap(event)) return;
679
996
  store.set({
@@ -691,7 +1008,7 @@ var Picker = ({ box, theme }) => {
691
1008
  domElement.removeEventListener("pointerup", up);
692
1009
  };
693
1010
  }, [box, camera, domElement, invalidate, isTap, scene, span, store]);
694
- const [planes, setPlanes] = useState2(() => axesPlanes(box, camera.position));
1011
+ const [planes, setPlanes] = useState3(() => axesPlanes(box, camera.position));
695
1012
  const key = planes.map((plane) => plane.sign).join();
696
1013
  useFrame(() => {
697
1014
  const next = [camera.position.x, camera.position.y, camera.position.z].map((eye, axis) => eye - [centre.x, centre.y, centre.z][axis] < 0 ? -1 : 1).join();
@@ -717,11 +1034,11 @@ var Picker = ({ box, theme }) => {
717
1034
  store.set(plane.options);
718
1035
  }
719
1036
  });
720
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
721
- /* @__PURE__ */ jsxs3("group", { ref: previewRef, visible: false, children: [
722
- /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
723
- /* @__PURE__ */ jsx3("planeGeometry", { args: [previewSize, previewSize] }),
724
- /* @__PURE__ */ jsx3(
1037
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1038
+ /* @__PURE__ */ jsxs4("group", { ref: previewRef, visible: false, children: [
1039
+ /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1040
+ /* @__PURE__ */ jsx8("planeGeometry", { args: [previewSize, previewSize] }),
1041
+ /* @__PURE__ */ jsx8(
725
1042
  "meshBasicMaterial",
726
1043
  {
727
1044
  color: theme.sectionOutline,
@@ -732,14 +1049,14 @@ var Picker = ({ box, theme }) => {
732
1049
  }
733
1050
  )
734
1051
  ] }),
735
- /* @__PURE__ */ jsx3(
1052
+ /* @__PURE__ */ jsx8(
736
1053
  "lineLoop",
737
1054
  {
738
1055
  geometry: square,
739
1056
  scale: previewSize / 2,
740
1057
  renderOrder: SECTION_RENDER_ORDER.outline,
741
1058
  raycast: () => null,
742
- children: /* @__PURE__ */ jsx3(
1059
+ children: /* @__PURE__ */ jsx8(
743
1060
  "lineBasicMaterial",
744
1061
  {
745
1062
  color: theme.sectionOutline,
@@ -752,15 +1069,15 @@ var Picker = ({ box, theme }) => {
752
1069
  }
753
1070
  )
754
1071
  ] }),
755
- planes.map((plane) => /* @__PURE__ */ jsxs3(
1072
+ planes.map((plane) => /* @__PURE__ */ jsxs4(
756
1073
  "group",
757
1074
  {
758
1075
  position: [plane.position.x, plane.position.y, plane.position.z],
759
1076
  rotation: AXIS_ROTATIONS[plane.axis],
760
1077
  children: [
761
- /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
762
- /* @__PURE__ */ jsx3("planeGeometry", { args: [plane.size, plane.size] }),
763
- /* @__PURE__ */ jsx3(
1078
+ /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
1079
+ /* @__PURE__ */ jsx8("planeGeometry", { args: [plane.size, plane.size] }),
1080
+ /* @__PURE__ */ jsx8(
764
1081
  "meshBasicMaterial",
765
1082
  {
766
1083
  color: AXIS_COLORS[plane.axis],
@@ -771,14 +1088,14 @@ var Picker = ({ box, theme }) => {
771
1088
  }
772
1089
  )
773
1090
  ] }),
774
- /* @__PURE__ */ jsx3(
1091
+ /* @__PURE__ */ jsx8(
775
1092
  "lineLoop",
776
1093
  {
777
1094
  geometry: square,
778
1095
  scale: plane.size / 2,
779
1096
  renderOrder: SECTION_RENDER_ORDER.outline,
780
1097
  raycast: () => null,
781
- children: /* @__PURE__ */ jsx3("lineBasicMaterial", { color: AXIS_COLORS[plane.axis], transparent: true, opacity: 0.7 })
1098
+ children: /* @__PURE__ */ jsx8("lineBasicMaterial", { color: AXIS_COLORS[plane.axis], transparent: true, opacity: 0.7 })
782
1099
  }
783
1100
  )
784
1101
  ]
@@ -789,9 +1106,9 @@ var Picker = ({ box, theme }) => {
789
1106
  };
790
1107
  var CutSheet = ({ box, section, theme }) => {
791
1108
  const square = useSquare();
792
- const cut = useMemo3(() => resolveSectionPlane(section, box), [box, section]);
793
- const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
794
- const placement = useMemo3(() => {
1109
+ const cut = useMemo5(() => resolveSectionPlane(section, box), [box, section]);
1110
+ const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1111
+ const placement = useMemo5(() => {
795
1112
  if (!cut) return null;
796
1113
  const position = sectionAnchor(box, cut.plane).addScaledVector(
797
1114
  cut.plane.normal,
@@ -801,10 +1118,10 @@ var CutSheet = ({ box, section, theme }) => {
801
1118
  }, [box, cut, span]);
802
1119
  if (!placement) return null;
803
1120
  const size = span * OUTLINE_SCALE;
804
- return /* @__PURE__ */ jsxs3("group", { position: placement.position, quaternion: placement.quaternion, children: [
805
- /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
806
- /* @__PURE__ */ jsx3("planeGeometry", { args: [size, size] }),
807
- /* @__PURE__ */ jsx3(
1121
+ return /* @__PURE__ */ jsxs4("group", { position: placement.position, quaternion: placement.quaternion, children: [
1122
+ /* @__PURE__ */ jsxs4("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
1123
+ /* @__PURE__ */ jsx8("planeGeometry", { args: [size, size] }),
1124
+ /* @__PURE__ */ jsx8(
808
1125
  "meshBasicMaterial",
809
1126
  {
810
1127
  color: theme.sectionOutline,
@@ -815,14 +1132,14 @@ var CutSheet = ({ box, section, theme }) => {
815
1132
  }
816
1133
  )
817
1134
  ] }),
818
- /* @__PURE__ */ jsx3(
1135
+ /* @__PURE__ */ jsx8(
819
1136
  "lineLoop",
820
1137
  {
821
1138
  geometry: square,
822
1139
  scale: size / 2,
823
1140
  renderOrder: SECTION_RENDER_ORDER.outline,
824
1141
  raycast: () => null,
825
- children: /* @__PURE__ */ jsx3(
1142
+ children: /* @__PURE__ */ jsx8(
826
1143
  "lineBasicMaterial",
827
1144
  {
828
1145
  color: theme.sectionOutline,
@@ -839,25 +1156,32 @@ var CutSheet = ({ box, section, theme }) => {
839
1156
 
840
1157
  // src/measure-tool.tsx
841
1158
  import { Html, Line } from "@react-three/drei";
842
- import { useFrame as useFrame2, useThree as useThree4 } from "@react-three/fiber";
843
- import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
1159
+ import { useFrame as useFrame2, useThree as useThree6 } from "@react-three/fiber";
1160
+ import {
1161
+ useEffect as useEffect5,
1162
+ useLayoutEffect as useLayoutEffect3,
1163
+ useMemo as useMemo6,
1164
+ useRef as useRef4,
1165
+ useState as useState4,
1166
+ useSyncExternalStore as useSyncExternalStore2
1167
+ } from "react";
844
1168
  import {
845
1169
  Matrix4,
846
1170
  Raycaster as Raycaster2,
847
1171
  SphereGeometry,
848
- Vector2 as Vector23,
849
- Vector3 as Vector36
1172
+ Vector2 as Vector22,
1173
+ Vector3 as Vector38
850
1174
  } from "three";
851
1175
 
852
1176
  // src/render/resample.ts
853
- import { Vector3 as Vector35 } from "three";
1177
+ import { Vector3 as Vector37 } from "three";
854
1178
  var ON_PLANE = 1e-7;
855
1179
  var STITCH = 1e-5;
856
1180
  var COLLINEAR = 1e-6;
857
1181
  function clipSegments(positions, plane) {
858
1182
  const out = [];
859
- const a = new Vector35();
860
- const b = new Vector35();
1183
+ const a = new Vector37();
1184
+ const b = new Vector37();
861
1185
  const count = Math.floor(positions.length / 6);
862
1186
  for (let i = 0; i < count; i += 1) {
863
1187
  a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
@@ -872,11 +1196,11 @@ function clipSegments(positions, plane) {
872
1196
  return Float32Array.from(out);
873
1197
  }
874
1198
  function sectionContour(positions, regionOf, plane) {
875
- const a = new Vector35();
876
- const b = new Vector35();
877
- const c = new Vector35();
878
- const p = new Vector35();
879
- const q = new Vector35();
1199
+ const a = new Vector37();
1200
+ const b = new Vector37();
1201
+ const c = new Vector37();
1202
+ const p = new Vector37();
1203
+ const q = new Vector37();
880
1204
  const byRegion = /* @__PURE__ */ new Map();
881
1205
  const triangleCount = Math.floor(positions.length / 9);
882
1206
  for (let t = 0; t < triangleCount; t += 1) {
@@ -934,17 +1258,17 @@ function stitch(segments, out) {
934
1258
  return into.set(segments[o], segments[o + 1], segments[o + 2]);
935
1259
  };
936
1260
  const direction = (i, into) => {
937
- const s = new Vector35();
938
- const e = new Vector35();
1261
+ const s = new Vector37();
1262
+ const e = new Vector37();
939
1263
  point(i, 0, s);
940
1264
  point(i, 1, e);
941
1265
  return into.subVectors(e, s).normalize();
942
1266
  };
943
1267
  const consumed = new Uint8Array(count);
944
- const d = new Vector35();
945
- const n = new Vector35();
1268
+ const d = new Vector37();
1269
+ const n = new Vector37();
946
1270
  const next = (i, end) => {
947
- const at = point(i, end, new Vector35());
1271
+ const at = point(i, end, new Vector37());
948
1272
  const list = ends.get(key(at.x, at.y, at.z)) ?? [];
949
1273
  if (list.length !== 2) return null;
950
1274
  const other = list.find((entry) => entry >> 1 !== i);
@@ -973,8 +1297,8 @@ function stitch(segments, out) {
973
1297
  };
974
1298
  const back = walk(0);
975
1299
  const forward = walk(1);
976
- const s = point(back.index, back.end, new Vector35());
977
- const e = point(forward.index, forward.end, new Vector35());
1300
+ const s = point(back.index, back.end, new Vector37());
1301
+ const e = point(forward.index, forward.end, new Vector37());
978
1302
  out.push(s.x, s.y, s.z, e.x, e.y, e.z);
979
1303
  }
980
1304
  }
@@ -982,8 +1306,8 @@ function pointInContour(point, plane, contour) {
982
1306
  const [u, v] = planeAxes(plane);
983
1307
  const px = point.dot(u);
984
1308
  const py = point.dot(v);
985
- const a = new Vector35();
986
- const b = new Vector35();
1309
+ const a = new Vector37();
1310
+ const b = new Vector37();
987
1311
  let inside = false;
988
1312
  const count = Math.floor(contour.length / 6);
989
1313
  for (let i = 0; i < count; i += 1) {
@@ -1000,15 +1324,15 @@ function pointInContour(point, plane, contour) {
1000
1324
  return inside;
1001
1325
  }
1002
1326
  function capHit(ray, plane, contour) {
1003
- const point = ray.intersectPlane(plane, new Vector35());
1327
+ const point = ray.intersectPlane(plane, new Vector37());
1004
1328
  if (!point) return null;
1005
1329
  return pointInContour(point, plane, contour) ? point : null;
1006
1330
  }
1007
1331
  function planeAxes(plane) {
1008
1332
  const n = plane.normal;
1009
- const seed = Math.abs(n.x) < 0.9 ? new Vector35(1, 0, 0) : new Vector35(0, 1, 0);
1010
- const u = new Vector35().crossVectors(n, seed).normalize();
1011
- const v = new Vector35().crossVectors(n, u).normalize();
1333
+ const seed = Math.abs(n.x) < 0.9 ? new Vector37(1, 0, 0) : new Vector37(0, 1, 0);
1334
+ const u = new Vector37().crossVectors(n, seed).normalize();
1335
+ const v = new Vector37().crossVectors(n, u).normalize();
1012
1336
  return [u, v];
1013
1337
  }
1014
1338
  function sampleSection(meshPositions, regionOf, edgePositions, plane) {
@@ -1021,8 +1345,8 @@ function sampleSection(meshPositions, regionOf, edgePositions, plane) {
1021
1345
  }
1022
1346
 
1023
1347
  // src/measure-tool.tsx
1024
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1025
- var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
1348
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1349
+ var FURNITURE5 = { [EXCLUDE_FROM_FRAME]: true };
1026
1350
  var MEASURE_LABEL_CLASS = "toolpath-measure-label";
1027
1351
  var MeasureTool = ({
1028
1352
  mode = "distance",
@@ -1035,25 +1359,26 @@ var MeasureTool = ({
1035
1359
  }) => {
1036
1360
  const store = useSectionStore();
1037
1361
  const box = useContentBox();
1038
- const resolved = useMemo4(() => resolveTheme(theme), [theme]);
1039
- const [held, setHeld] = useState3([]);
1362
+ const resolved = useMemo6(() => resolveTheme(theme), [theme]);
1363
+ const picking = useSyncExternalStore2(store.subscribe, store.isPicking, store.isPicking);
1364
+ const [held, setHeld] = useState4([]);
1040
1365
  const controlled = measurements !== void 0;
1041
1366
  const list = controlled ? measurements : held;
1042
- const [draft, setDraft] = useState3([]);
1043
- useEffect3(() => {
1367
+ const [draft, setDraft] = useState4([]);
1368
+ useEffect5(() => {
1044
1369
  store.setEngaged(true);
1045
1370
  return () => store.setEngaged(false);
1046
1371
  }, [store]);
1047
- useEffect3(() => {
1372
+ useEffect5(() => {
1048
1373
  setDraft([]);
1049
1374
  }, [mode]);
1050
1375
  const commit = (next) => {
1051
1376
  if (!controlled) setHeld(next);
1052
1377
  onChange?.(next);
1053
1378
  };
1054
- const latest = useRef3({ list, commit });
1379
+ const latest = useRef4({ list, commit });
1055
1380
  latest.current = { list, commit };
1056
- useEffect3(() => {
1381
+ useEffect5(() => {
1057
1382
  const onKey = (event) => {
1058
1383
  if (event.key === "Escape") {
1059
1384
  setDraft([]);
@@ -1081,8 +1406,8 @@ var MeasureTool = ({
1081
1406
  };
1082
1407
  if (box.isEmpty()) return null;
1083
1408
  const label = className(labelClassName);
1084
- return /* @__PURE__ */ jsxs4("group", { userData: FURNITURE4, children: [
1085
- /* @__PURE__ */ jsx4(
1409
+ return /* @__PURE__ */ jsxs5("group", { userData: FURNITURE5, children: [
1410
+ picking ? null : /* @__PURE__ */ jsx9(
1086
1411
  Snapper,
1087
1412
  {
1088
1413
  mode,
@@ -1093,7 +1418,7 @@ var MeasureTool = ({
1093
1418
  label
1094
1419
  }
1095
1420
  ),
1096
- draft.map((point, index) => /* @__PURE__ */ jsx4(
1421
+ draft.map((point, index) => /* @__PURE__ */ jsx9(
1097
1422
  ScreenDot,
1098
1423
  {
1099
1424
  point,
@@ -1102,8 +1427,8 @@ var MeasureTool = ({
1102
1427
  },
1103
1428
  index
1104
1429
  )),
1105
- draft.length === 2 ? /* @__PURE__ */ jsx4(Segment, { points: [draft[0], draft[1]], color: resolved.measure }) : null,
1106
- list.map((measurement) => /* @__PURE__ */ jsx4(
1430
+ draft.length === 2 ? /* @__PURE__ */ jsx9(Segment, { points: [draft[0], draft[1]], color: resolved.measure }) : null,
1431
+ list.map((measurement) => /* @__PURE__ */ jsx9(
1107
1432
  MeasurementView,
1108
1433
  {
1109
1434
  measurement,
@@ -1117,30 +1442,30 @@ var MeasureTool = ({
1117
1442
  ] });
1118
1443
  };
1119
1444
  var NO_POINTER = { pointerEvents: "none" };
1120
- var NOWHERE = [new Vector36(), new Vector36()];
1445
+ var NOWHERE = [new Vector38(), new Vector38()];
1121
1446
  var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1122
- const camera = useThree4((state) => state.camera);
1123
- const scene = useThree4((state) => state.scene);
1124
- const size = useThree4((state) => state.size);
1125
- const domElement = useThree4((state) => state.gl.domElement);
1126
- const invalidate = useThree4((state) => state.invalidate);
1447
+ const camera = useThree6((state) => state.camera);
1448
+ const scene = useThree6((state) => state.scene);
1449
+ const size = useThree6((state) => state.size);
1450
+ const domElement = useThree6((state) => state.gl.domElement);
1451
+ const invalidate = useThree6((state) => state.invalidate);
1127
1452
  const isTap = useTapGuard();
1128
- const marker = useRef3(null);
1129
- const edgeLine = useRef3(null);
1130
- const band = useRef3(null);
1131
- const bandLabel = useRef3(null);
1132
- const bandText = useRef3(null);
1133
- const snap = useRef3(null);
1453
+ const marker = useRef4(null);
1454
+ const edgeLine = useRef4(null);
1455
+ const band = useRef4(null);
1456
+ const bandLabel = useRef4(null);
1457
+ const bandText = useRef4(null);
1458
+ const snap = useRef4(null);
1134
1459
  const dot = useDot();
1135
- useLayoutEffect(() => {
1460
+ useLayoutEffect3(() => {
1136
1461
  if (edgeLine.current) edgeLine.current.visible = false;
1137
1462
  if (band.current) band.current.visible = false;
1138
1463
  }, []);
1139
- const current = useRef3({ mode, draft, onPlace, format, size });
1464
+ const current = useRef4({ mode, draft, onPlace, format, size });
1140
1465
  current.current = { mode, draft, onPlace, format, size };
1141
- useEffect3(() => {
1466
+ useEffect5(() => {
1142
1467
  const raycaster = new Raycaster2();
1143
- const pointer = new Vector23();
1468
+ const pointer = new Vector22();
1144
1469
  const samples = new SampleCache();
1145
1470
  const hide = () => {
1146
1471
  if (!snap.current) return;
@@ -1170,7 +1495,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1170
1495
  const snapped = snapAt(target, target.edges, camera, viewport);
1171
1496
  const from = placed[placed.length - 1];
1172
1497
  const lock = shift && from ? lockToAxis(from, snapped.point) : null;
1173
- const found = lock ? { ...snapped, point: new Vector36(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1498
+ const found = lock ? { ...snapped, point: new Vector38(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1174
1499
  snap.current = found;
1175
1500
  const node = marker.current;
1176
1501
  if (node) {
@@ -1212,8 +1537,14 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1212
1537
  if (event.key !== "Shift" || !last || !snap.current) return;
1213
1538
  update(last.x, last.y, event.type === "keydown");
1214
1539
  };
1540
+ let pressed = false;
1541
+ const down = () => {
1542
+ pressed = true;
1543
+ };
1215
1544
  const up = (event) => {
1216
- if (event.button !== 0 || !isTap(event)) return;
1545
+ const seen = pressed;
1546
+ pressed = false;
1547
+ if (!seen || event.button !== 0 || !isTap(event)) return;
1217
1548
  update(event.clientX, event.clientY, event.shiftKey);
1218
1549
  const found = snap.current;
1219
1550
  if (!found) return;
@@ -1225,18 +1556,20 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1225
1556
  };
1226
1557
  domElement.addEventListener("pointermove", move);
1227
1558
  domElement.addEventListener("pointerleave", leave);
1559
+ domElement.addEventListener("pointerdown", down);
1228
1560
  domElement.addEventListener("pointerup", up);
1229
1561
  window.addEventListener("keydown", key);
1230
1562
  window.addEventListener("keyup", key);
1231
1563
  return () => {
1232
1564
  domElement.removeEventListener("pointermove", move);
1233
1565
  domElement.removeEventListener("pointerleave", leave);
1566
+ domElement.removeEventListener("pointerdown", down);
1234
1567
  domElement.removeEventListener("pointerup", up);
1235
1568
  window.removeEventListener("keydown", key);
1236
1569
  window.removeEventListener("keyup", key);
1237
1570
  };
1238
1571
  }, [camera, domElement, invalidate, isTap, scene, theme.measure]);
1239
- useEffect3(() => {
1572
+ useEffect5(() => {
1240
1573
  if (draft.length === 0 && band.current) {
1241
1574
  band.current.visible = false;
1242
1575
  if (bandLabel.current) bandLabel.current.visible = false;
@@ -1247,9 +1580,9 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1247
1580
  invalidate();
1248
1581
  }
1249
1582
  }, [draft, invalidate]);
1250
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1251
- /* @__PURE__ */ jsx4("group", { ref: marker, visible: false, children: /* @__PURE__ */ jsx4("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx4("meshBasicMaterial", { color: theme.measureSnap, depthTest: false, depthWrite: false }) }) }),
1252
- /* @__PURE__ */ jsx4(
1583
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1584
+ /* @__PURE__ */ jsx9("group", { ref: marker, visible: false, children: /* @__PURE__ */ jsx9("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx9("meshBasicMaterial", { color: theme.measureSnap, depthTest: false, depthWrite: false }) }) }),
1585
+ /* @__PURE__ */ jsx9(
1253
1586
  Line,
1254
1587
  {
1255
1588
  ref: edgeLine,
@@ -1263,7 +1596,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1263
1596
  raycast: () => null
1264
1597
  }
1265
1598
  ),
1266
- /* @__PURE__ */ jsx4(
1599
+ /* @__PURE__ */ jsx9(
1267
1600
  Line,
1268
1601
  {
1269
1602
  ref: band,
@@ -1277,7 +1610,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1277
1610
  raycast: () => null
1278
1611
  }
1279
1612
  ),
1280
- /* @__PURE__ */ jsx4("group", { ref: bandLabel, visible: false, children: /* @__PURE__ */ jsx4(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx4(
1613
+ /* @__PURE__ */ jsx9("group", { ref: bandLabel, visible: false, children: /* @__PURE__ */ jsx9(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx9(
1281
1614
  "div",
1282
1615
  {
1283
1616
  ref: bandText,
@@ -1442,35 +1775,35 @@ var MeasurementView = ({
1442
1775
  const anchor = measurementLabelAnchor(measurement);
1443
1776
  if (measurement.kind === "distance") {
1444
1777
  const [a2, b2] = measurement.points;
1445
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1446
- /* @__PURE__ */ jsx4(Segment, { points: [a2, b2], color: theme.measure }),
1447
- /* @__PURE__ */ jsx4(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1448
- /* @__PURE__ */ jsx4(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1449
- showDeltas ? /* @__PURE__ */ jsx4(DeltaLegs, { a: a2, b: b2, id: measurement.id, format, label }) : null,
1450
- /* @__PURE__ */ jsx4(Label, { at: anchor, text, kind: "distance", id: measurement.id, className: label })
1778
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1779
+ /* @__PURE__ */ jsx9(Segment, { points: [a2, b2], color: theme.measure }),
1780
+ /* @__PURE__ */ jsx9(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1781
+ /* @__PURE__ */ jsx9(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1782
+ showDeltas ? /* @__PURE__ */ jsx9(DeltaLegs, { a: a2, b: b2, id: measurement.id, format, label }) : null,
1783
+ /* @__PURE__ */ jsx9(Label, { at: anchor, text, kind: "distance", id: measurement.id, className: label })
1451
1784
  ] });
1452
1785
  }
1453
1786
  const [a, vertex, b] = measurement.points;
1454
1787
  const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b));
1455
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1456
- /* @__PURE__ */ jsx4(Segment, { points: [vertex, a], color: theme.measure }),
1457
- /* @__PURE__ */ jsx4(Segment, { points: [vertex, b], color: theme.measure }),
1458
- /* @__PURE__ */ jsx4(Polyline, { points: arc, color: theme.measure }),
1459
- /* @__PURE__ */ jsx4(ScreenDot, { point: a, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1460
- /* @__PURE__ */ jsx4(ScreenDot, { point: vertex, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1461
- /* @__PURE__ */ jsx4(ScreenDot, { point: b, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1462
- /* @__PURE__ */ jsx4(Label, { at: anchor, text, kind: "angle", id: measurement.id, className: label })
1788
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1789
+ /* @__PURE__ */ jsx9(Segment, { points: [vertex, a], color: theme.measure }),
1790
+ /* @__PURE__ */ jsx9(Segment, { points: [vertex, b], color: theme.measure }),
1791
+ /* @__PURE__ */ jsx9(Polyline, { points: arc, color: theme.measure }),
1792
+ /* @__PURE__ */ jsx9(ScreenDot, { point: a, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1793
+ /* @__PURE__ */ jsx9(ScreenDot, { point: vertex, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1794
+ /* @__PURE__ */ jsx9(ScreenDot, { point: b, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1795
+ /* @__PURE__ */ jsx9(Label, { at: anchor, text, kind: "angle", id: measurement.id, className: label })
1463
1796
  ] });
1464
1797
  };
1465
1798
  var DeltaLegs = ({ a, b, id, format, label }) => {
1466
- const camera = useThree4((state) => state.camera);
1467
- const size = useThree4((state) => state.size);
1468
- const invalidate = useThree4((state) => state.invalidate);
1469
- const legs = useMemo4(() => deltaLegs(a, b), [a, b]);
1470
- const lines = useRef3([]);
1471
- const centre = useMemo4(() => {
1799
+ const camera = useThree6((state) => state.camera);
1800
+ const size = useThree6((state) => state.size);
1801
+ const invalidate = useThree6((state) => state.invalidate);
1802
+ const legs = useMemo6(() => deltaLegs(a, b), [a, b]);
1803
+ const lines = useRef4([]);
1804
+ const centre = useMemo6(() => {
1472
1805
  const at = midpoint(a, b);
1473
- return new Vector36(at.x, at.y, at.z);
1806
+ return new Vector38(at.x, at.y, at.z);
1474
1807
  }, [a, b]);
1475
1808
  useFrame2(() => {
1476
1809
  const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
@@ -1484,16 +1817,16 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
1484
1817
  }
1485
1818
  if (changed) invalidate();
1486
1819
  });
1487
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1488
- legs.map((leg, index) => /* @__PURE__ */ jsx4(
1820
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1821
+ legs.map((leg, index) => /* @__PURE__ */ jsx9(
1489
1822
  Line,
1490
1823
  {
1491
1824
  ref: (line) => {
1492
1825
  lines.current[index] = line;
1493
1826
  },
1494
1827
  points: [
1495
- new Vector36(leg.from.x, leg.from.y, leg.from.z),
1496
- new Vector36(leg.to.x, leg.to.y, leg.to.z)
1828
+ new Vector38(leg.from.x, leg.from.y, leg.from.z),
1829
+ new Vector38(leg.to.x, leg.to.y, leg.to.z)
1497
1830
  ],
1498
1831
  color: AXIS_COLORS[leg.axis],
1499
1832
  lineWidth: MEASURE_LINE_PIXELS.leg,
@@ -1508,7 +1841,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
1508
1841
  },
1509
1842
  leg.axis
1510
1843
  )),
1511
- legs.map((leg) => /* @__PURE__ */ jsx4(
1844
+ legs.map((leg) => /* @__PURE__ */ jsx9(
1512
1845
  Label,
1513
1846
  {
1514
1847
  at: midpoint(leg.from, leg.to),
@@ -1522,14 +1855,14 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
1522
1855
  ))
1523
1856
  ] });
1524
1857
  };
1525
- var Label = ({ at, text, kind, id, axis, className: name }) => /* @__PURE__ */ jsx4("group", { position: [at.x, at.y, at.z], children: /* @__PURE__ */ jsx4(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx4("div", { className: name, "data-measure-label": kind, "data-measurement-id": id, "data-axis": axis, children: text }) }) });
1858
+ var Label = ({ at, text, kind, id, axis, className: name }) => /* @__PURE__ */ jsx9("group", { position: [at.x, at.y, at.z], children: /* @__PURE__ */ jsx9(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx9("div", { className: name, "data-measure-label": kind, "data-measurement-id": id, "data-axis": axis, children: text }) }) });
1526
1859
  var ScreenDot = ({ point, pixels, color }) => {
1527
- const camera = useThree4((state) => state.camera);
1528
- const size = useThree4((state) => state.size);
1529
- const invalidate = useThree4((state) => state.invalidate);
1530
- const node = useRef3(null);
1860
+ const camera = useThree6((state) => state.camera);
1861
+ const size = useThree6((state) => state.size);
1862
+ const invalidate = useThree6((state) => state.invalidate);
1863
+ const node = useRef4(null);
1531
1864
  const dot = useDot();
1532
- const position = useMemo4(() => new Vector36(point.x, point.y, point.z), [point]);
1865
+ const position = useMemo6(() => new Vector38(point.x, point.y, point.z), [point]);
1533
1866
  useFrame2(() => {
1534
1867
  const group = node.current;
1535
1868
  if (!group) return;
@@ -1539,21 +1872,21 @@ var ScreenDot = ({ point, pixels, color }) => {
1539
1872
  invalidate();
1540
1873
  }
1541
1874
  });
1542
- return /* @__PURE__ */ jsx4("group", { ref: node, position, children: /* @__PURE__ */ jsx4("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx4("meshBasicMaterial", { color, depthTest: false, depthWrite: false }) }) });
1875
+ return /* @__PURE__ */ jsx9("group", { ref: node, position, children: /* @__PURE__ */ jsx9("mesh", { geometry: dot, renderOrder: MEASURE_RENDER_ORDER, raycast: () => null, children: /* @__PURE__ */ jsx9("meshBasicMaterial", { color, depthTest: false, depthWrite: false }) }) });
1543
1876
  };
1544
1877
  var Segment = ({ points, color }) => {
1545
1878
  const [a, b] = points;
1546
- const ends = useMemo4(
1547
- () => [new Vector36(a.x, a.y, a.z), new Vector36(b.x, b.y, b.z)],
1879
+ const ends = useMemo6(
1880
+ () => [new Vector38(a.x, a.y, a.z), new Vector38(b.x, b.y, b.z)],
1548
1881
  // Keyed on the numbers: a draft's tuple is a fresh array on every render,
1549
1882
  // and a rebuilt array would rebuild the line's geometry with it.
1550
1883
  [a.x, a.y, a.z, b.x, b.y, b.z]
1551
1884
  );
1552
- return /* @__PURE__ */ jsx4(Polyline, { points: ends, color });
1885
+ return /* @__PURE__ */ jsx9(Polyline, { points: ends, color });
1553
1886
  };
1554
1887
  var Polyline = ({ points, color }) => {
1555
1888
  if (points.length < 2) return null;
1556
- return /* @__PURE__ */ jsx4(
1889
+ return /* @__PURE__ */ jsx9(
1557
1890
  Line,
1558
1891
  {
1559
1892
  points,
@@ -1568,8 +1901,8 @@ var Polyline = ({ points, color }) => {
1568
1901
  );
1569
1902
  };
1570
1903
  function useDot() {
1571
- const geometry = useMemo4(() => new SphereGeometry(1, 16, 12), []);
1572
- useEffect3(() => () => geometry.dispose(), [geometry]);
1904
+ const geometry = useMemo6(() => new SphereGeometry(1, 16, 12), []);
1905
+ useEffect5(() => () => geometry.dispose(), [geometry]);
1573
1906
  return geometry;
1574
1907
  }
1575
1908
  function isEditing(target) {
@@ -1620,10 +1953,15 @@ export {
1620
1953
  AXES_PLANE_SCALE,
1621
1954
  AXIS_COLORS,
1622
1955
  Axes,
1956
+ BANANA_GAP,
1957
+ BANANA_MODEL_URL,
1958
+ Banana,
1959
+ BoxStock,
1623
1960
  CAD_CAMERA_UP,
1624
1961
  CANDIDATE_WEIGHT,
1625
1962
  CAP_HATCH,
1626
1963
  CHAMFER,
1964
+ CONTROL_SCHEME_OPTIONS,
1627
1965
  CadCameraControls,
1628
1966
  DEFAULT_FIT_MARGIN,
1629
1967
  DEFAULT_SECTION_NORMAL,
@@ -1642,6 +1980,7 @@ export {
1642
1980
  HIGHLIGHT_COLORS,
1643
1981
  HIGHLIGHT_WEIGHT,
1644
1982
  HOVER_WEIGHT,
1983
+ HoverCard,
1645
1984
  MAX_FRAME_RATIO,
1646
1985
  MEASURE_LABEL_CLASS,
1647
1986
  MEASURE_LINE_PIXELS,
@@ -1666,17 +2005,21 @@ export {
1666
2005
  PartMesh,
1667
2006
  PartReportFormatError,
1668
2007
  REGION_ATTRIBUTE,
2008
+ SECTION_GIZMO_FRAME_SCALE,
1669
2009
  SECTION_RENDER_ORDER,
1670
2010
  SNAP_MARKER_PIXELS,
1671
2011
  SNAP_PIXELS,
1672
2012
  SectionTool,
1673
2013
  SectionView,
2014
+ Stock,
1674
2015
  TAP_SLOP,
1675
2016
  UnsupportedKernelVersionError,
1676
2017
  VIEW_NAMES,
1677
2018
  VIEW_SIGNS,
1678
2019
  ViewCube,
1679
2020
  Viewer,
2021
+ ViewerToolbar,
2022
+ ViewerToolbarProvider,
1680
2023
  adaptedUp,
1681
2024
  angleArc,
1682
2025
  angleArcRadius,
@@ -1687,8 +2030,11 @@ export {
1687
2030
  arrowPlacement,
1688
2031
  aspectRatio,
1689
2032
  axesPlanes,
2033
+ bananaFrameBounds,
2034
+ bananaPosition,
1690
2035
  bestOwner,
1691
2036
  boundsFromBox,
2037
+ boxStockBounds,
1692
2038
  buildPick,
1693
2039
  buildRegionAttribute,
1694
2040
  buildRegionIndex,
@@ -1712,6 +2058,7 @@ export {
1712
2058
  deltaBetween,
1713
2059
  deltaLegs,
1714
2060
  directionColor,
2061
+ directionHighlights,
1715
2062
  directionIndexOf,
1716
2063
  directionLabel,
1717
2064
  distanceBetween,
@@ -1719,6 +2066,7 @@ export {
1719
2066
  excludedFromFrame,
1720
2067
  featureTypeRank,
1721
2068
  fitDistance,
2069
+ fixedBoxStockBounds,
1722
2070
  focusForPick,
1723
2071
  formatDegrees,
1724
2072
  formatMillimetres,
@@ -1761,6 +2109,7 @@ export {
1761
2109
  sectionDepthConstant,
1762
2110
  sectionDepthRange,
1763
2111
  sectionFromPick,
2112
+ sectionMeasurement,
1764
2113
  sectionOffset,
1765
2114
  sectionOptionsFromState,
1766
2115
  sectionPlane,
@@ -1778,6 +2127,7 @@ export {
1778
2127
  useSectionStore,
1779
2128
  useTapGuard,
1780
2129
  useViewerControls,
2130
+ useViewerToolbar,
1781
2131
  viewDirection,
1782
2132
  viewKind,
1783
2133
  viewUp,