@toolpath/viewer 1.4.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-HH3W5T42.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,43 +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(() => {
648
960
  store.setPicking(true);
649
961
  return () => store.setPicking(false);
650
962
  }, [store]);
651
- useEffect2(() => {
963
+ useEffect4(() => {
652
964
  const raycaster = new Raycaster();
653
- const pointer = new Vector22();
654
- const aim = new Vector34();
965
+ const pointer = new Vector2();
966
+ const aim = new Vector36();
655
967
  const hide = () => {
656
968
  if (!hover.current) return;
657
969
  hover.current = null;
@@ -678,6 +990,7 @@ var Picker = ({ box, theme }) => {
678
990
  invalidate();
679
991
  };
680
992
  const up = (event) => {
993
+ move(event);
681
994
  const hit = hover.current;
682
995
  if (event.button !== 0 || !hit || !isTap(event)) return;
683
996
  store.set({
@@ -695,7 +1008,7 @@ var Picker = ({ box, theme }) => {
695
1008
  domElement.removeEventListener("pointerup", up);
696
1009
  };
697
1010
  }, [box, camera, domElement, invalidate, isTap, scene, span, store]);
698
- const [planes, setPlanes] = useState2(() => axesPlanes(box, camera.position));
1011
+ const [planes, setPlanes] = useState3(() => axesPlanes(box, camera.position));
699
1012
  const key = planes.map((plane) => plane.sign).join();
700
1013
  useFrame(() => {
701
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();
@@ -721,11 +1034,11 @@ var Picker = ({ box, theme }) => {
721
1034
  store.set(plane.options);
722
1035
  }
723
1036
  });
724
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
725
- /* @__PURE__ */ jsxs3("group", { ref: previewRef, visible: false, children: [
726
- /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
727
- /* @__PURE__ */ jsx3("planeGeometry", { args: [previewSize, previewSize] }),
728
- /* @__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(
729
1042
  "meshBasicMaterial",
730
1043
  {
731
1044
  color: theme.sectionOutline,
@@ -736,14 +1049,14 @@ var Picker = ({ box, theme }) => {
736
1049
  }
737
1050
  )
738
1051
  ] }),
739
- /* @__PURE__ */ jsx3(
1052
+ /* @__PURE__ */ jsx8(
740
1053
  "lineLoop",
741
1054
  {
742
1055
  geometry: square,
743
1056
  scale: previewSize / 2,
744
1057
  renderOrder: SECTION_RENDER_ORDER.outline,
745
1058
  raycast: () => null,
746
- children: /* @__PURE__ */ jsx3(
1059
+ children: /* @__PURE__ */ jsx8(
747
1060
  "lineBasicMaterial",
748
1061
  {
749
1062
  color: theme.sectionOutline,
@@ -756,15 +1069,15 @@ var Picker = ({ box, theme }) => {
756
1069
  }
757
1070
  )
758
1071
  ] }),
759
- planes.map((plane) => /* @__PURE__ */ jsxs3(
1072
+ planes.map((plane) => /* @__PURE__ */ jsxs4(
760
1073
  "group",
761
1074
  {
762
1075
  position: [plane.position.x, plane.position.y, plane.position.z],
763
1076
  rotation: AXIS_ROTATIONS[plane.axis],
764
1077
  children: [
765
- /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, ...planeHandlers(plane), children: [
766
- /* @__PURE__ */ jsx3("planeGeometry", { args: [plane.size, plane.size] }),
767
- /* @__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(
768
1081
  "meshBasicMaterial",
769
1082
  {
770
1083
  color: AXIS_COLORS[plane.axis],
@@ -775,14 +1088,14 @@ var Picker = ({ box, theme }) => {
775
1088
  }
776
1089
  )
777
1090
  ] }),
778
- /* @__PURE__ */ jsx3(
1091
+ /* @__PURE__ */ jsx8(
779
1092
  "lineLoop",
780
1093
  {
781
1094
  geometry: square,
782
1095
  scale: plane.size / 2,
783
1096
  renderOrder: SECTION_RENDER_ORDER.outline,
784
1097
  raycast: () => null,
785
- 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 })
786
1099
  }
787
1100
  )
788
1101
  ]
@@ -793,9 +1106,9 @@ var Picker = ({ box, theme }) => {
793
1106
  };
794
1107
  var CutSheet = ({ box, section, theme }) => {
795
1108
  const square = useSquare();
796
- const cut = useMemo3(() => resolveSectionPlane(section, box), [box, section]);
797
- const span = useMemo3(() => box.getSize(new Vector34()).length(), [box]);
798
- 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(() => {
799
1112
  if (!cut) return null;
800
1113
  const position = sectionAnchor(box, cut.plane).addScaledVector(
801
1114
  cut.plane.normal,
@@ -805,10 +1118,10 @@ var CutSheet = ({ box, section, theme }) => {
805
1118
  }, [box, cut, span]);
806
1119
  if (!placement) return null;
807
1120
  const size = span * OUTLINE_SCALE;
808
- return /* @__PURE__ */ jsxs3("group", { position: placement.position, quaternion: placement.quaternion, children: [
809
- /* @__PURE__ */ jsxs3("mesh", { renderOrder: SECTION_RENDER_ORDER.outline, raycast: () => null, children: [
810
- /* @__PURE__ */ jsx3("planeGeometry", { args: [size, size] }),
811
- /* @__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(
812
1125
  "meshBasicMaterial",
813
1126
  {
814
1127
  color: theme.sectionOutline,
@@ -819,14 +1132,14 @@ var CutSheet = ({ box, section, theme }) => {
819
1132
  }
820
1133
  )
821
1134
  ] }),
822
- /* @__PURE__ */ jsx3(
1135
+ /* @__PURE__ */ jsx8(
823
1136
  "lineLoop",
824
1137
  {
825
1138
  geometry: square,
826
1139
  scale: size / 2,
827
1140
  renderOrder: SECTION_RENDER_ORDER.outline,
828
1141
  raycast: () => null,
829
- children: /* @__PURE__ */ jsx3(
1142
+ children: /* @__PURE__ */ jsx8(
830
1143
  "lineBasicMaterial",
831
1144
  {
832
1145
  color: theme.sectionOutline,
@@ -843,32 +1156,32 @@ var CutSheet = ({ box, section, theme }) => {
843
1156
 
844
1157
  // src/measure-tool.tsx
845
1158
  import { Html, Line } from "@react-three/drei";
846
- import { useFrame as useFrame2, useThree as useThree4 } from "@react-three/fiber";
1159
+ import { useFrame as useFrame2, useThree as useThree6 } from "@react-three/fiber";
847
1160
  import {
848
- useEffect as useEffect3,
849
- useLayoutEffect,
850
- useMemo as useMemo4,
851
- useRef as useRef3,
852
- useState as useState3,
1161
+ useEffect as useEffect5,
1162
+ useLayoutEffect as useLayoutEffect3,
1163
+ useMemo as useMemo6,
1164
+ useRef as useRef4,
1165
+ useState as useState4,
853
1166
  useSyncExternalStore as useSyncExternalStore2
854
1167
  } from "react";
855
1168
  import {
856
1169
  Matrix4,
857
1170
  Raycaster as Raycaster2,
858
1171
  SphereGeometry,
859
- Vector2 as Vector23,
860
- Vector3 as Vector36
1172
+ Vector2 as Vector22,
1173
+ Vector3 as Vector38
861
1174
  } from "three";
862
1175
 
863
1176
  // src/render/resample.ts
864
- import { Vector3 as Vector35 } from "three";
1177
+ import { Vector3 as Vector37 } from "three";
865
1178
  var ON_PLANE = 1e-7;
866
1179
  var STITCH = 1e-5;
867
1180
  var COLLINEAR = 1e-6;
868
1181
  function clipSegments(positions, plane) {
869
1182
  const out = [];
870
- const a = new Vector35();
871
- const b = new Vector35();
1183
+ const a = new Vector37();
1184
+ const b = new Vector37();
872
1185
  const count = Math.floor(positions.length / 6);
873
1186
  for (let i = 0; i < count; i += 1) {
874
1187
  a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
@@ -883,11 +1196,11 @@ function clipSegments(positions, plane) {
883
1196
  return Float32Array.from(out);
884
1197
  }
885
1198
  function sectionContour(positions, regionOf, plane) {
886
- const a = new Vector35();
887
- const b = new Vector35();
888
- const c = new Vector35();
889
- const p = new Vector35();
890
- 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();
891
1204
  const byRegion = /* @__PURE__ */ new Map();
892
1205
  const triangleCount = Math.floor(positions.length / 9);
893
1206
  for (let t = 0; t < triangleCount; t += 1) {
@@ -945,17 +1258,17 @@ function stitch(segments, out) {
945
1258
  return into.set(segments[o], segments[o + 1], segments[o + 2]);
946
1259
  };
947
1260
  const direction = (i, into) => {
948
- const s = new Vector35();
949
- const e = new Vector35();
1261
+ const s = new Vector37();
1262
+ const e = new Vector37();
950
1263
  point(i, 0, s);
951
1264
  point(i, 1, e);
952
1265
  return into.subVectors(e, s).normalize();
953
1266
  };
954
1267
  const consumed = new Uint8Array(count);
955
- const d = new Vector35();
956
- const n = new Vector35();
1268
+ const d = new Vector37();
1269
+ const n = new Vector37();
957
1270
  const next = (i, end) => {
958
- const at = point(i, end, new Vector35());
1271
+ const at = point(i, end, new Vector37());
959
1272
  const list = ends.get(key(at.x, at.y, at.z)) ?? [];
960
1273
  if (list.length !== 2) return null;
961
1274
  const other = list.find((entry) => entry >> 1 !== i);
@@ -984,8 +1297,8 @@ function stitch(segments, out) {
984
1297
  };
985
1298
  const back = walk(0);
986
1299
  const forward = walk(1);
987
- const s = point(back.index, back.end, new Vector35());
988
- 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());
989
1302
  out.push(s.x, s.y, s.z, e.x, e.y, e.z);
990
1303
  }
991
1304
  }
@@ -993,8 +1306,8 @@ function pointInContour(point, plane, contour) {
993
1306
  const [u, v] = planeAxes(plane);
994
1307
  const px = point.dot(u);
995
1308
  const py = point.dot(v);
996
- const a = new Vector35();
997
- const b = new Vector35();
1309
+ const a = new Vector37();
1310
+ const b = new Vector37();
998
1311
  let inside = false;
999
1312
  const count = Math.floor(contour.length / 6);
1000
1313
  for (let i = 0; i < count; i += 1) {
@@ -1011,15 +1324,15 @@ function pointInContour(point, plane, contour) {
1011
1324
  return inside;
1012
1325
  }
1013
1326
  function capHit(ray, plane, contour) {
1014
- const point = ray.intersectPlane(plane, new Vector35());
1327
+ const point = ray.intersectPlane(plane, new Vector37());
1015
1328
  if (!point) return null;
1016
1329
  return pointInContour(point, plane, contour) ? point : null;
1017
1330
  }
1018
1331
  function planeAxes(plane) {
1019
1332
  const n = plane.normal;
1020
- const seed = Math.abs(n.x) < 0.9 ? new Vector35(1, 0, 0) : new Vector35(0, 1, 0);
1021
- const u = new Vector35().crossVectors(n, seed).normalize();
1022
- 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();
1023
1336
  return [u, v];
1024
1337
  }
1025
1338
  function sampleSection(meshPositions, regionOf, edgePositions, plane) {
@@ -1032,8 +1345,8 @@ function sampleSection(meshPositions, regionOf, edgePositions, plane) {
1032
1345
  }
1033
1346
 
1034
1347
  // src/measure-tool.tsx
1035
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1036
- 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 };
1037
1350
  var MEASURE_LABEL_CLASS = "toolpath-measure-label";
1038
1351
  var MeasureTool = ({
1039
1352
  mode = "distance",
@@ -1046,26 +1359,26 @@ var MeasureTool = ({
1046
1359
  }) => {
1047
1360
  const store = useSectionStore();
1048
1361
  const box = useContentBox();
1049
- const resolved = useMemo4(() => resolveTheme(theme), [theme]);
1362
+ const resolved = useMemo6(() => resolveTheme(theme), [theme]);
1050
1363
  const picking = useSyncExternalStore2(store.subscribe, store.isPicking, store.isPicking);
1051
- const [held, setHeld] = useState3([]);
1364
+ const [held, setHeld] = useState4([]);
1052
1365
  const controlled = measurements !== void 0;
1053
1366
  const list = controlled ? measurements : held;
1054
- const [draft, setDraft] = useState3([]);
1055
- useEffect3(() => {
1367
+ const [draft, setDraft] = useState4([]);
1368
+ useEffect5(() => {
1056
1369
  store.setEngaged(true);
1057
1370
  return () => store.setEngaged(false);
1058
1371
  }, [store]);
1059
- useEffect3(() => {
1372
+ useEffect5(() => {
1060
1373
  setDraft([]);
1061
1374
  }, [mode]);
1062
1375
  const commit = (next) => {
1063
1376
  if (!controlled) setHeld(next);
1064
1377
  onChange?.(next);
1065
1378
  };
1066
- const latest = useRef3({ list, commit });
1379
+ const latest = useRef4({ list, commit });
1067
1380
  latest.current = { list, commit };
1068
- useEffect3(() => {
1381
+ useEffect5(() => {
1069
1382
  const onKey = (event) => {
1070
1383
  if (event.key === "Escape") {
1071
1384
  setDraft([]);
@@ -1093,8 +1406,8 @@ var MeasureTool = ({
1093
1406
  };
1094
1407
  if (box.isEmpty()) return null;
1095
1408
  const label = className(labelClassName);
1096
- return /* @__PURE__ */ jsxs4("group", { userData: FURNITURE4, children: [
1097
- picking ? null : /* @__PURE__ */ jsx4(
1409
+ return /* @__PURE__ */ jsxs5("group", { userData: FURNITURE5, children: [
1410
+ picking ? null : /* @__PURE__ */ jsx9(
1098
1411
  Snapper,
1099
1412
  {
1100
1413
  mode,
@@ -1105,7 +1418,7 @@ var MeasureTool = ({
1105
1418
  label
1106
1419
  }
1107
1420
  ),
1108
- draft.map((point, index) => /* @__PURE__ */ jsx4(
1421
+ draft.map((point, index) => /* @__PURE__ */ jsx9(
1109
1422
  ScreenDot,
1110
1423
  {
1111
1424
  point,
@@ -1114,8 +1427,8 @@ var MeasureTool = ({
1114
1427
  },
1115
1428
  index
1116
1429
  )),
1117
- draft.length === 2 ? /* @__PURE__ */ jsx4(Segment, { points: [draft[0], draft[1]], color: resolved.measure }) : null,
1118
- 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(
1119
1432
  MeasurementView,
1120
1433
  {
1121
1434
  measurement,
@@ -1129,30 +1442,30 @@ var MeasureTool = ({
1129
1442
  ] });
1130
1443
  };
1131
1444
  var NO_POINTER = { pointerEvents: "none" };
1132
- var NOWHERE = [new Vector36(), new Vector36()];
1445
+ var NOWHERE = [new Vector38(), new Vector38()];
1133
1446
  var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1134
- const camera = useThree4((state) => state.camera);
1135
- const scene = useThree4((state) => state.scene);
1136
- const size = useThree4((state) => state.size);
1137
- const domElement = useThree4((state) => state.gl.domElement);
1138
- 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);
1139
1452
  const isTap = useTapGuard();
1140
- const marker = useRef3(null);
1141
- const edgeLine = useRef3(null);
1142
- const band = useRef3(null);
1143
- const bandLabel = useRef3(null);
1144
- const bandText = useRef3(null);
1145
- 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);
1146
1459
  const dot = useDot();
1147
- useLayoutEffect(() => {
1460
+ useLayoutEffect3(() => {
1148
1461
  if (edgeLine.current) edgeLine.current.visible = false;
1149
1462
  if (band.current) band.current.visible = false;
1150
1463
  }, []);
1151
- const current = useRef3({ mode, draft, onPlace, format, size });
1464
+ const current = useRef4({ mode, draft, onPlace, format, size });
1152
1465
  current.current = { mode, draft, onPlace, format, size };
1153
- useEffect3(() => {
1466
+ useEffect5(() => {
1154
1467
  const raycaster = new Raycaster2();
1155
- const pointer = new Vector23();
1468
+ const pointer = new Vector22();
1156
1469
  const samples = new SampleCache();
1157
1470
  const hide = () => {
1158
1471
  if (!snap.current) return;
@@ -1182,7 +1495,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1182
1495
  const snapped = snapAt(target, target.edges, camera, viewport);
1183
1496
  const from = placed[placed.length - 1];
1184
1497
  const lock = shift && from ? lockToAxis(from, snapped.point) : null;
1185
- 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;
1186
1499
  snap.current = found;
1187
1500
  const node = marker.current;
1188
1501
  if (node) {
@@ -1256,7 +1569,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1256
1569
  window.removeEventListener("keyup", key);
1257
1570
  };
1258
1571
  }, [camera, domElement, invalidate, isTap, scene, theme.measure]);
1259
- useEffect3(() => {
1572
+ useEffect5(() => {
1260
1573
  if (draft.length === 0 && band.current) {
1261
1574
  band.current.visible = false;
1262
1575
  if (bandLabel.current) bandLabel.current.visible = false;
@@ -1267,9 +1580,9 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1267
1580
  invalidate();
1268
1581
  }
1269
1582
  }, [draft, invalidate]);
1270
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1271
- /* @__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 }) }) }),
1272
- /* @__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(
1273
1586
  Line,
1274
1587
  {
1275
1588
  ref: edgeLine,
@@ -1283,7 +1596,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1283
1596
  raycast: () => null
1284
1597
  }
1285
1598
  ),
1286
- /* @__PURE__ */ jsx4(
1599
+ /* @__PURE__ */ jsx9(
1287
1600
  Line,
1288
1601
  {
1289
1602
  ref: band,
@@ -1297,7 +1610,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1297
1610
  raycast: () => null
1298
1611
  }
1299
1612
  ),
1300
- /* @__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(
1301
1614
  "div",
1302
1615
  {
1303
1616
  ref: bandText,
@@ -1462,35 +1775,35 @@ var MeasurementView = ({
1462
1775
  const anchor = measurementLabelAnchor(measurement);
1463
1776
  if (measurement.kind === "distance") {
1464
1777
  const [a2, b2] = measurement.points;
1465
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1466
- /* @__PURE__ */ jsx4(Segment, { points: [a2, b2], color: theme.measure }),
1467
- /* @__PURE__ */ jsx4(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1468
- /* @__PURE__ */ jsx4(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1469
- showDeltas ? /* @__PURE__ */ jsx4(DeltaLegs, { a: a2, b: b2, id: measurement.id, format, label }) : null,
1470
- /* @__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 })
1471
1784
  ] });
1472
1785
  }
1473
1786
  const [a, vertex, b] = measurement.points;
1474
1787
  const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b));
1475
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1476
- /* @__PURE__ */ jsx4(Segment, { points: [vertex, a], color: theme.measure }),
1477
- /* @__PURE__ */ jsx4(Segment, { points: [vertex, b], color: theme.measure }),
1478
- /* @__PURE__ */ jsx4(Polyline, { points: arc, color: theme.measure }),
1479
- /* @__PURE__ */ jsx4(ScreenDot, { point: a, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1480
- /* @__PURE__ */ jsx4(ScreenDot, { point: vertex, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1481
- /* @__PURE__ */ jsx4(ScreenDot, { point: b, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1482
- /* @__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 })
1483
1796
  ] });
1484
1797
  };
1485
1798
  var DeltaLegs = ({ a, b, id, format, label }) => {
1486
- const camera = useThree4((state) => state.camera);
1487
- const size = useThree4((state) => state.size);
1488
- const invalidate = useThree4((state) => state.invalidate);
1489
- const legs = useMemo4(() => deltaLegs(a, b), [a, b]);
1490
- const lines = useRef3([]);
1491
- 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(() => {
1492
1805
  const at = midpoint(a, b);
1493
- return new Vector36(at.x, at.y, at.z);
1806
+ return new Vector38(at.x, at.y, at.z);
1494
1807
  }, [a, b]);
1495
1808
  useFrame2(() => {
1496
1809
  const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
@@ -1504,16 +1817,16 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
1504
1817
  }
1505
1818
  if (changed) invalidate();
1506
1819
  });
1507
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1508
- legs.map((leg, index) => /* @__PURE__ */ jsx4(
1820
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1821
+ legs.map((leg, index) => /* @__PURE__ */ jsx9(
1509
1822
  Line,
1510
1823
  {
1511
1824
  ref: (line) => {
1512
1825
  lines.current[index] = line;
1513
1826
  },
1514
1827
  points: [
1515
- new Vector36(leg.from.x, leg.from.y, leg.from.z),
1516
- 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)
1517
1830
  ],
1518
1831
  color: AXIS_COLORS[leg.axis],
1519
1832
  lineWidth: MEASURE_LINE_PIXELS.leg,
@@ -1528,7 +1841,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
1528
1841
  },
1529
1842
  leg.axis
1530
1843
  )),
1531
- legs.map((leg) => /* @__PURE__ */ jsx4(
1844
+ legs.map((leg) => /* @__PURE__ */ jsx9(
1532
1845
  Label,
1533
1846
  {
1534
1847
  at: midpoint(leg.from, leg.to),
@@ -1542,14 +1855,14 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
1542
1855
  ))
1543
1856
  ] });
1544
1857
  };
1545
- 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 }) }) });
1546
1859
  var ScreenDot = ({ point, pixels, color }) => {
1547
- const camera = useThree4((state) => state.camera);
1548
- const size = useThree4((state) => state.size);
1549
- const invalidate = useThree4((state) => state.invalidate);
1550
- 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);
1551
1864
  const dot = useDot();
1552
- 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]);
1553
1866
  useFrame2(() => {
1554
1867
  const group = node.current;
1555
1868
  if (!group) return;
@@ -1559,21 +1872,21 @@ var ScreenDot = ({ point, pixels, color }) => {
1559
1872
  invalidate();
1560
1873
  }
1561
1874
  });
1562
- 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 }) }) });
1563
1876
  };
1564
1877
  var Segment = ({ points, color }) => {
1565
1878
  const [a, b] = points;
1566
- const ends = useMemo4(
1567
- () => [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)],
1568
1881
  // Keyed on the numbers: a draft's tuple is a fresh array on every render,
1569
1882
  // and a rebuilt array would rebuild the line's geometry with it.
1570
1883
  [a.x, a.y, a.z, b.x, b.y, b.z]
1571
1884
  );
1572
- return /* @__PURE__ */ jsx4(Polyline, { points: ends, color });
1885
+ return /* @__PURE__ */ jsx9(Polyline, { points: ends, color });
1573
1886
  };
1574
1887
  var Polyline = ({ points, color }) => {
1575
1888
  if (points.length < 2) return null;
1576
- return /* @__PURE__ */ jsx4(
1889
+ return /* @__PURE__ */ jsx9(
1577
1890
  Line,
1578
1891
  {
1579
1892
  points,
@@ -1588,8 +1901,8 @@ var Polyline = ({ points, color }) => {
1588
1901
  );
1589
1902
  };
1590
1903
  function useDot() {
1591
- const geometry = useMemo4(() => new SphereGeometry(1, 16, 12), []);
1592
- useEffect3(() => () => geometry.dispose(), [geometry]);
1904
+ const geometry = useMemo6(() => new SphereGeometry(1, 16, 12), []);
1905
+ useEffect5(() => () => geometry.dispose(), [geometry]);
1593
1906
  return geometry;
1594
1907
  }
1595
1908
  function isEditing(target) {
@@ -1640,10 +1953,15 @@ export {
1640
1953
  AXES_PLANE_SCALE,
1641
1954
  AXIS_COLORS,
1642
1955
  Axes,
1956
+ BANANA_GAP,
1957
+ BANANA_MODEL_URL,
1958
+ Banana,
1959
+ BoxStock,
1643
1960
  CAD_CAMERA_UP,
1644
1961
  CANDIDATE_WEIGHT,
1645
1962
  CAP_HATCH,
1646
1963
  CHAMFER,
1964
+ CONTROL_SCHEME_OPTIONS,
1647
1965
  CadCameraControls,
1648
1966
  DEFAULT_FIT_MARGIN,
1649
1967
  DEFAULT_SECTION_NORMAL,
@@ -1662,6 +1980,7 @@ export {
1662
1980
  HIGHLIGHT_COLORS,
1663
1981
  HIGHLIGHT_WEIGHT,
1664
1982
  HOVER_WEIGHT,
1983
+ HoverCard,
1665
1984
  MAX_FRAME_RATIO,
1666
1985
  MEASURE_LABEL_CLASS,
1667
1986
  MEASURE_LINE_PIXELS,
@@ -1686,17 +2005,21 @@ export {
1686
2005
  PartMesh,
1687
2006
  PartReportFormatError,
1688
2007
  REGION_ATTRIBUTE,
2008
+ SECTION_GIZMO_FRAME_SCALE,
1689
2009
  SECTION_RENDER_ORDER,
1690
2010
  SNAP_MARKER_PIXELS,
1691
2011
  SNAP_PIXELS,
1692
2012
  SectionTool,
1693
2013
  SectionView,
2014
+ Stock,
1694
2015
  TAP_SLOP,
1695
2016
  UnsupportedKernelVersionError,
1696
2017
  VIEW_NAMES,
1697
2018
  VIEW_SIGNS,
1698
2019
  ViewCube,
1699
2020
  Viewer,
2021
+ ViewerToolbar,
2022
+ ViewerToolbarProvider,
1700
2023
  adaptedUp,
1701
2024
  angleArc,
1702
2025
  angleArcRadius,
@@ -1707,8 +2030,11 @@ export {
1707
2030
  arrowPlacement,
1708
2031
  aspectRatio,
1709
2032
  axesPlanes,
2033
+ bananaFrameBounds,
2034
+ bananaPosition,
1710
2035
  bestOwner,
1711
2036
  boundsFromBox,
2037
+ boxStockBounds,
1712
2038
  buildPick,
1713
2039
  buildRegionAttribute,
1714
2040
  buildRegionIndex,
@@ -1732,6 +2058,7 @@ export {
1732
2058
  deltaBetween,
1733
2059
  deltaLegs,
1734
2060
  directionColor,
2061
+ directionHighlights,
1735
2062
  directionIndexOf,
1736
2063
  directionLabel,
1737
2064
  distanceBetween,
@@ -1739,6 +2066,7 @@ export {
1739
2066
  excludedFromFrame,
1740
2067
  featureTypeRank,
1741
2068
  fitDistance,
2069
+ fixedBoxStockBounds,
1742
2070
  focusForPick,
1743
2071
  formatDegrees,
1744
2072
  formatMillimetres,
@@ -1781,6 +2109,7 @@ export {
1781
2109
  sectionDepthConstant,
1782
2110
  sectionDepthRange,
1783
2111
  sectionFromPick,
2112
+ sectionMeasurement,
1784
2113
  sectionOffset,
1785
2114
  sectionOptionsFromState,
1786
2115
  sectionPlane,
@@ -1798,6 +2127,7 @@ export {
1798
2127
  useSectionStore,
1799
2128
  useTapGuard,
1800
2129
  useViewerControls,
2130
+ useViewerToolbar,
1801
2131
  viewDirection,
1802
2132
  viewKind,
1803
2133
  viewUp,