@toolpath/viewer 1.1.1 → 1.2.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,12 +1,17 @@
1
1
  import {
2
+ AXES_PLANE_OFFSET,
3
+ AXES_PLANE_SCALE,
2
4
  CAD_CAMERA_UP,
3
5
  CANDIDATE_WEIGHT,
6
+ CAP_HATCH,
4
7
  CHAMFER,
5
8
  CONE_AXIS,
6
9
  CadCameraControls,
7
10
  DEFAULT_FIT_MARGIN,
11
+ DEFAULT_SECTION_NORMAL,
8
12
  DEFAULT_THEME,
9
13
  DIRECTION_COLORS,
14
+ DISABLED_SECTION,
10
15
  DOUBLE_TAP_MS,
11
16
  EXCLUDE_FROM_FRAME,
12
17
  EnginePart,
@@ -29,8 +34,10 @@ import {
29
34
  ORBIT_TARGET_RING_OPACITY,
30
35
  ORBIT_TARGET_RING_PIXELS,
31
36
  ORBIT_TARGET_RING_WIDTH,
37
+ OUTLINE_SCALE,
32
38
  PERSPECTIVE_FOV,
33
39
  PICKED_SURFACE_LABEL,
40
+ PREVIEW_SCALE,
34
41
  PartMesh,
35
42
  PartReportFormatError,
36
43
  REGION_ATTRIBUTE,
@@ -43,10 +50,12 @@ import {
43
50
  VIEW_SIGNS,
44
51
  Viewer,
45
52
  adaptedUp,
53
+ applyCapTheme,
46
54
  applyHighlightLayers,
47
55
  applyProjection,
48
56
  arrowPlacement,
49
57
  aspectRatio,
58
+ axesPlanes,
50
59
  bestOwner,
51
60
  boundsFromBox,
52
61
  buildPick,
@@ -56,7 +65,12 @@ import {
56
65
  cadViewDirections,
57
66
  cameraLimits,
58
67
  contentBounds,
68
+ createCapMaterial,
69
+ createMaskMaterial,
70
+ createMaskTarget,
59
71
  createPart,
72
+ createSectionStore,
73
+ createStencilMaterials,
60
74
  cubeOutlineGeometry,
61
75
  cubeZones,
62
76
  currentViewDirection,
@@ -66,10 +80,12 @@ import {
66
80
  directionIndexOf,
67
81
  directionLabel,
68
82
  dragPlane,
83
+ excludedFromFrame,
69
84
  featureTypeRank,
70
85
  fitDistance,
71
86
  focusForPick,
72
87
  groupByDirection,
88
+ hitUnderRay,
73
89
  labelGeometry,
74
90
  labelTexture,
75
91
  movedFar,
@@ -87,6 +103,7 @@ import {
87
103
  retargetPose,
88
104
  sameDirection,
89
105
  screenLength,
106
+ sectionAnchor,
90
107
  sectionBounds,
91
108
  sectionConstant,
92
109
  sectionDepth,
@@ -94,16 +111,19 @@ import {
94
111
  sectionDepthRange,
95
112
  sectionFromPick,
96
113
  sectionOffset,
114
+ sectionOptionsFromState,
97
115
  sectionPlane,
98
116
  smoothRegionNormals,
99
117
  squaredUp,
100
118
  startPosition,
119
+ surfaceUnderRay,
101
120
  targetBoundary,
102
121
  themesEqual,
103
122
  trackDoubleTaps,
104
123
  trackTaps,
105
124
  useContentBox,
106
125
  useRetarget,
126
+ useSectionStore,
107
127
  useTapGuard,
108
128
  useViewerControls,
109
129
  viewDirection,
@@ -111,7 +131,7 @@ import {
111
131
  viewUp,
112
132
  viewVector,
113
133
  visualSurfaces
114
- } from "./chunk-4BU4QZCL.js";
134
+ } from "./chunk-HWAEUJ4H.js";
115
135
 
116
136
  // src/primitives.tsx
117
137
  import { GizmoHelper } from "@react-three/drei";
@@ -354,6 +374,936 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
354
374
  ] });
355
375
  };
356
376
 
377
+ // 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";
380
+ import {
381
+ BufferGeometry as BufferGeometry2,
382
+ DoubleSide,
383
+ Quaternion as Quaternion2,
384
+ Raycaster,
385
+ Vector2 as Vector22,
386
+ Vector3 as Vector34
387
+ } 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);
593
+ var AXIS_ROTATIONS = {
594
+ x: [0, Math.PI / 2, 0],
595
+ y: [Math.PI / 2, 0, 0],
596
+ z: [0, 0, 0]
597
+ };
598
+ var PREVIEW_LIFT = 2e-3;
599
+ var SHEET_SETBACK = 1e-3;
600
+ var SectionTool = ({ theme }) => {
601
+ const store = useSectionStore();
602
+ const section = useSyncExternalStore(store.subscribe, store.get, store.get);
603
+ const box = useContentBox();
604
+ const resolved = useMemo3(() => resolveTheme(theme), [theme]);
605
+ useEffect2(() => {
606
+ store.setEngaged(true);
607
+ return () => store.setEngaged(false);
608
+ }, [store]);
609
+ useEffect2(() => {
610
+ if (!section) return void 0;
611
+ const clear = (event) => {
612
+ if (event.key === "Escape") store.set(null);
613
+ };
614
+ window.addEventListener("keydown", clear);
615
+ return () => window.removeEventListener("keydown", clear);
616
+ }, [section, store]);
617
+ 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 }) });
619
+ };
620
+ var useSquare = () => {
621
+ const square = useMemo3(
622
+ () => 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)
627
+ ]),
628
+ []
629
+ );
630
+ useEffect2(() => () => square.dispose(), [square]);
631
+ return square;
632
+ };
633
+ var Picker = ({ box, theme }) => {
634
+ 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);
639
+ const isTap = useTapGuard();
640
+ 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]);
646
+ const previewSize = span * PREVIEW_SCALE;
647
+ useEffect2(() => {
648
+ const raycaster = new Raycaster();
649
+ const pointer = new Vector22();
650
+ const aim = new Vector34();
651
+ const hide = () => {
652
+ if (!hover.current) return;
653
+ hover.current = null;
654
+ if (previewRef.current) previewRef.current.visible = false;
655
+ invalidate();
656
+ };
657
+ const move = (event) => {
658
+ const rect = domElement.getBoundingClientRect();
659
+ pointer.set(
660
+ (event.clientX - rect.left) / rect.width * 2 - 1,
661
+ -((event.clientY - rect.top) / rect.height) * 2 + 1
662
+ );
663
+ raycaster.setFromCamera(pointer, camera);
664
+ const hit = surfaceUnderRay(raycaster, scene);
665
+ const preview = previewRef.current;
666
+ if (!hit || !preview) {
667
+ hide();
668
+ return;
669
+ }
670
+ hover.current = hit;
671
+ preview.position.copy(hit.point).addScaledVector(hit.normal, span * PREVIEW_LIFT);
672
+ preview.lookAt(aim.copy(hit.point).add(hit.normal));
673
+ preview.visible = true;
674
+ invalidate();
675
+ };
676
+ const up = (event) => {
677
+ const hit = hover.current;
678
+ if (event.button !== 0 || !hit || !isTap(event)) return;
679
+ store.set({
680
+ enabled: true,
681
+ plane: sectionFromPick({ point: hit.point, normal: hit.normal }),
682
+ depth: pickedStartDepth(box)
683
+ });
684
+ };
685
+ domElement.addEventListener("pointermove", move);
686
+ domElement.addEventListener("pointerleave", hide);
687
+ domElement.addEventListener("pointerup", up);
688
+ return () => {
689
+ domElement.removeEventListener("pointermove", move);
690
+ domElement.removeEventListener("pointerleave", hide);
691
+ domElement.removeEventListener("pointerup", up);
692
+ };
693
+ }, [box, camera, domElement, invalidate, isTap, scene, span, store]);
694
+ const [planes, setPlanes] = useState2(() => axesPlanes(box, camera.position));
695
+ const key = planes.map((plane) => plane.sign).join();
696
+ useFrame(() => {
697
+ 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();
698
+ if (next !== key) setPlanes(axesPlanes(box, camera.position));
699
+ });
700
+ const planeHandlers = (plane) => ({
701
+ // A plane is behind the part, and the part does not stop propagation: a
702
+ // click on a face reaches the plane behind it too. The preview says which
703
+ // one the pointer is really on.
704
+ onPointerOver: (event) => {
705
+ if (hover.current) return;
706
+ event.stopPropagation();
707
+ setHoveredPlane(plane.axis);
708
+ invalidate();
709
+ },
710
+ onPointerOut: () => {
711
+ setHoveredPlane((held) => held === plane.axis ? null : held);
712
+ invalidate();
713
+ },
714
+ onClick: (event) => {
715
+ if (hover.current || !isTap(event.nativeEvent)) return;
716
+ event.stopPropagation();
717
+ store.set(plane.options);
718
+ }
719
+ });
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(
725
+ "meshBasicMaterial",
726
+ {
727
+ color: theme.sectionOutline,
728
+ transparent: true,
729
+ opacity: 0.2,
730
+ side: DoubleSide,
731
+ depthWrite: false
732
+ }
733
+ )
734
+ ] }),
735
+ /* @__PURE__ */ jsx3(
736
+ "lineLoop",
737
+ {
738
+ geometry: square,
739
+ scale: previewSize / 2,
740
+ renderOrder: SECTION_RENDER_ORDER.outline,
741
+ raycast: () => null,
742
+ children: /* @__PURE__ */ jsx3(
743
+ "lineBasicMaterial",
744
+ {
745
+ color: theme.sectionOutline,
746
+ transparent: true,
747
+ opacity: 0.5,
748
+ depthTest: false,
749
+ depthWrite: false
750
+ }
751
+ )
752
+ }
753
+ )
754
+ ] }),
755
+ planes.map((plane) => /* @__PURE__ */ jsxs3(
756
+ "group",
757
+ {
758
+ position: [plane.position.x, plane.position.y, plane.position.z],
759
+ rotation: AXIS_ROTATIONS[plane.axis],
760
+ 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(
764
+ "meshBasicMaterial",
765
+ {
766
+ color: AXIS_COLORS[plane.axis],
767
+ transparent: true,
768
+ opacity: hoveredPlane === plane.axis ? 0.45 : 0.3,
769
+ side: DoubleSide,
770
+ depthWrite: false
771
+ }
772
+ )
773
+ ] }),
774
+ /* @__PURE__ */ jsx3(
775
+ "lineLoop",
776
+ {
777
+ geometry: square,
778
+ scale: plane.size / 2,
779
+ renderOrder: SECTION_RENDER_ORDER.outline,
780
+ raycast: () => null,
781
+ children: /* @__PURE__ */ jsx3("lineBasicMaterial", { color: AXIS_COLORS[plane.axis], transparent: true, opacity: 0.7 })
782
+ }
783
+ )
784
+ ]
785
+ },
786
+ plane.axis
787
+ ))
788
+ ] });
789
+ };
790
+ var CutSheet = ({ box, section, theme }) => {
791
+ 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(() => {
795
+ if (!cut) return null;
796
+ const position = sectionAnchor(box, cut.plane).addScaledVector(
797
+ cut.plane.normal,
798
+ -span * SHEET_SETBACK
799
+ );
800
+ return { position, quaternion: new Quaternion2().setFromUnitVectors(Z, cut.plane.normal) };
801
+ }, [box, cut, span]);
802
+ if (!placement) return null;
803
+ 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(
808
+ "meshBasicMaterial",
809
+ {
810
+ color: theme.sectionOutline,
811
+ transparent: true,
812
+ opacity: 0.1,
813
+ side: DoubleSide,
814
+ depthWrite: false
815
+ }
816
+ )
817
+ ] }),
818
+ /* @__PURE__ */ jsx3(
819
+ "lineLoop",
820
+ {
821
+ geometry: square,
822
+ scale: size / 2,
823
+ renderOrder: SECTION_RENDER_ORDER.outline,
824
+ raycast: () => null,
825
+ children: /* @__PURE__ */ jsx3(
826
+ "lineBasicMaterial",
827
+ {
828
+ color: theme.sectionOutline,
829
+ transparent: true,
830
+ opacity: 0.5,
831
+ depthTest: false,
832
+ depthWrite: false
833
+ }
834
+ )
835
+ }
836
+ )
837
+ ] });
838
+ };
839
+
840
+ // src/measure-tool.tsx
841
+ 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";
844
+ import {
845
+ Raycaster as Raycaster2,
846
+ SphereGeometry,
847
+ Vector2 as Vector23,
848
+ Vector3 as Vector35
849
+ } from "three";
850
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
851
+ var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
852
+ var MEASURE_LABEL_CLASS = "toolpath-measure-label";
853
+ var MeasureTool = ({
854
+ mode = "distance",
855
+ measurements,
856
+ onChange,
857
+ showDeltas = true,
858
+ format = formatMillimetres,
859
+ labelClassName,
860
+ theme
861
+ }) => {
862
+ const store = useSectionStore();
863
+ const box = useContentBox();
864
+ const resolved = useMemo4(() => resolveTheme(theme), [theme]);
865
+ const [held, setHeld] = useState3([]);
866
+ const controlled = measurements !== void 0;
867
+ const list = controlled ? measurements : held;
868
+ const [draft, setDraft] = useState3([]);
869
+ useEffect3(() => {
870
+ store.setEngaged(true);
871
+ return () => store.setEngaged(false);
872
+ }, [store]);
873
+ useEffect3(() => {
874
+ setDraft([]);
875
+ }, [mode]);
876
+ const commit = (next) => {
877
+ if (!controlled) setHeld(next);
878
+ onChange?.(next);
879
+ };
880
+ const latest = useRef3({ list, commit });
881
+ latest.current = { list, commit };
882
+ useEffect3(() => {
883
+ const onKey = (event) => {
884
+ if (event.key === "Escape") {
885
+ setDraft([]);
886
+ return;
887
+ }
888
+ if (event.key !== "Delete" && event.key !== "Backspace") return;
889
+ if (isEditing(event.target)) return;
890
+ const { list: current, commit: apply } = latest.current;
891
+ if (current.length === 0) return;
892
+ event.preventDefault();
893
+ apply(current.slice(0, -1));
894
+ };
895
+ window.addEventListener("keydown", onKey);
896
+ return () => window.removeEventListener("keydown", onKey);
897
+ }, []);
898
+ const place = (point) => {
899
+ const points = [...draft, point];
900
+ const complete = measurementFromPoints(mode, points, nextMeasurementId(list));
901
+ if (complete) {
902
+ commit([...list, complete]);
903
+ setDraft([]);
904
+ } else {
905
+ setDraft(points);
906
+ }
907
+ };
908
+ if (box.isEmpty()) return null;
909
+ const label = className(labelClassName);
910
+ return /* @__PURE__ */ jsxs4("group", { userData: FURNITURE4, children: [
911
+ /* @__PURE__ */ jsx4(
912
+ Snapper,
913
+ {
914
+ mode,
915
+ draft,
916
+ onPlace: place,
917
+ theme: resolved,
918
+ format,
919
+ label
920
+ }
921
+ ),
922
+ draft.map((point, index) => /* @__PURE__ */ jsx4(
923
+ ScreenDot,
924
+ {
925
+ point,
926
+ pixels: MEASURE_MARKER_PIXELS,
927
+ color: resolved.measure
928
+ },
929
+ index
930
+ )),
931
+ draft.length === 2 ? /* @__PURE__ */ jsx4(Segment, { points: [draft[0], draft[1]], color: resolved.measure }) : null,
932
+ list.map((measurement) => /* @__PURE__ */ jsx4(
933
+ MeasurementView,
934
+ {
935
+ measurement,
936
+ theme: resolved,
937
+ format,
938
+ showDeltas,
939
+ label
940
+ },
941
+ measurement.id
942
+ ))
943
+ ] });
944
+ };
945
+ var NO_POINTER = { pointerEvents: "none" };
946
+ var NOWHERE = [new Vector35(), new Vector35()];
947
+ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
948
+ const camera = useThree4((state) => state.camera);
949
+ const scene = useThree4((state) => state.scene);
950
+ const size = useThree4((state) => state.size);
951
+ const domElement = useThree4((state) => state.gl.domElement);
952
+ const invalidate = useThree4((state) => state.invalidate);
953
+ const isTap = useTapGuard();
954
+ const marker = useRef3(null);
955
+ const edgeLine = useRef3(null);
956
+ const band = useRef3(null);
957
+ const bandLabel = useRef3(null);
958
+ const bandText = useRef3(null);
959
+ const snap = useRef3(null);
960
+ const dot = useDot();
961
+ useLayoutEffect(() => {
962
+ if (edgeLine.current) edgeLine.current.visible = false;
963
+ if (band.current) band.current.visible = false;
964
+ }, []);
965
+ const current = useRef3({ mode, draft, onPlace, format, size });
966
+ current.current = { mode, draft, onPlace, format, size };
967
+ useEffect3(() => {
968
+ const raycaster = new Raycaster2();
969
+ const pointer = new Vector23();
970
+ const hide = () => {
971
+ if (!snap.current) return;
972
+ snap.current = null;
973
+ if (marker.current) marker.current.visible = false;
974
+ if (edgeLine.current) edgeLine.current.visible = false;
975
+ if (band.current) band.current.visible = false;
976
+ if (bandLabel.current) bandLabel.current.visible = false;
977
+ if (bandText.current) bandText.current.style.display = "none";
978
+ invalidate();
979
+ };
980
+ let last = null;
981
+ const update = (clientX, clientY, shift) => {
982
+ last = { x: clientX, y: clientY };
983
+ const rect = domElement.getBoundingClientRect();
984
+ pointer.set(
985
+ (clientX - rect.left) / rect.width * 2 - 1,
986
+ -((clientY - rect.top) / rect.height) * 2 + 1
987
+ );
988
+ raycaster.setFromCamera(pointer, camera);
989
+ const hit = hitUnderRay(raycaster, scene);
990
+ if (!hit?.face) {
991
+ hide();
992
+ return;
993
+ }
994
+ const normal = hit.face.normal.clone().transformDirection(hit.object.matrixWorld);
995
+ const { size: viewport, draft: placed, mode: kind, format: write } = current.current;
996
+ const snapped = snapAt(
997
+ { point: hit.point, normal },
998
+ edgesBeside(hit.object),
999
+ camera,
1000
+ viewport
1001
+ );
1002
+ const from = placed[placed.length - 1];
1003
+ const lock = shift && from ? lockToAxis(from, snapped.point) : null;
1004
+ const found = lock ? { ...snapped, point: new Vector35(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1005
+ snap.current = found;
1006
+ const node = marker.current;
1007
+ if (node) {
1008
+ node.position.copy(found.point);
1009
+ node.visible = true;
1010
+ node.scale.setScalar(
1011
+ screenLength(camera, found.point, viewport, SNAP_MARKER_PIXELS[found.kind])
1012
+ );
1013
+ }
1014
+ if (edgeLine.current) {
1015
+ edgeLine.current.visible = found.edge !== null;
1016
+ if (found.edge) moveLine(edgeLine.current, found.edge[0], found.edge[1]);
1017
+ }
1018
+ const text = liveLabel(kind, placed, found.point, write);
1019
+ if (band.current) {
1020
+ band.current.visible = from !== void 0;
1021
+ if (from) moveLine(band.current, from, found.point);
1022
+ setLineColor(band.current, lock ? AXIS_COLORS[lock.axis] : theme.measure);
1023
+ }
1024
+ if (bandLabel.current && from) {
1025
+ const at = midpoint(from, found.point);
1026
+ bandLabel.current.position.set(at.x, at.y, at.z);
1027
+ bandLabel.current.visible = text !== null;
1028
+ }
1029
+ if (bandText.current) {
1030
+ bandText.current.style.display = text === null ? "none" : "";
1031
+ bandText.current.textContent = text ?? "";
1032
+ }
1033
+ invalidate();
1034
+ };
1035
+ const move = (event) => {
1036
+ if (event.buttons !== 0) {
1037
+ hide();
1038
+ return;
1039
+ }
1040
+ update(event.clientX, event.clientY, event.shiftKey);
1041
+ };
1042
+ const key = (event) => {
1043
+ if (event.key !== "Shift" || !last || !snap.current) return;
1044
+ update(last.x, last.y, event.type === "keydown");
1045
+ };
1046
+ const up = (event) => {
1047
+ if (event.button !== 0 || !isTap(event)) return;
1048
+ update(event.clientX, event.clientY, event.shiftKey);
1049
+ const found = snap.current;
1050
+ if (!found) return;
1051
+ current.current.onPlace({ x: found.point.x, y: found.point.y, z: found.point.z });
1052
+ };
1053
+ const leave = () => {
1054
+ last = null;
1055
+ hide();
1056
+ };
1057
+ domElement.addEventListener("pointermove", move);
1058
+ domElement.addEventListener("pointerleave", leave);
1059
+ domElement.addEventListener("pointerup", up);
1060
+ window.addEventListener("keydown", key);
1061
+ window.addEventListener("keyup", key);
1062
+ return () => {
1063
+ domElement.removeEventListener("pointermove", move);
1064
+ domElement.removeEventListener("pointerleave", leave);
1065
+ domElement.removeEventListener("pointerup", up);
1066
+ window.removeEventListener("keydown", key);
1067
+ window.removeEventListener("keyup", key);
1068
+ };
1069
+ }, [camera, domElement, invalidate, isTap, scene, theme.measure]);
1070
+ useEffect3(() => {
1071
+ if (draft.length === 0 && band.current) {
1072
+ band.current.visible = false;
1073
+ if (bandLabel.current) bandLabel.current.visible = false;
1074
+ if (bandText.current) {
1075
+ bandText.current.style.display = "none";
1076
+ bandText.current.textContent = "";
1077
+ }
1078
+ invalidate();
1079
+ }
1080
+ }, [draft, invalidate]);
1081
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1082
+ /* @__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 }) }) }),
1083
+ /* @__PURE__ */ jsx4(
1084
+ Line,
1085
+ {
1086
+ ref: edgeLine,
1087
+ points: NOWHERE,
1088
+ color: theme.measureSnap,
1089
+ lineWidth: MEASURE_LINE_PIXELS.edge,
1090
+ depthTest: false,
1091
+ depthWrite: false,
1092
+ frustumCulled: false,
1093
+ renderOrder: MEASURE_RENDER_ORDER,
1094
+ raycast: () => null
1095
+ }
1096
+ ),
1097
+ /* @__PURE__ */ jsx4(
1098
+ Line,
1099
+ {
1100
+ ref: band,
1101
+ points: NOWHERE,
1102
+ color: theme.measure,
1103
+ lineWidth: MEASURE_LINE_PIXELS.line,
1104
+ depthTest: false,
1105
+ depthWrite: false,
1106
+ frustumCulled: false,
1107
+ renderOrder: MEASURE_RENDER_ORDER,
1108
+ raycast: () => null
1109
+ }
1110
+ ),
1111
+ /* @__PURE__ */ jsx4("group", { ref: bandLabel, visible: false, children: /* @__PURE__ */ jsx4(Html, { center: true, zIndexRange: [0, 0], style: NO_POINTER, children: /* @__PURE__ */ jsx4(
1112
+ "div",
1113
+ {
1114
+ ref: bandText,
1115
+ className: label,
1116
+ "data-measure-label": "live",
1117
+ style: { display: "none" }
1118
+ }
1119
+ ) }) })
1120
+ ] });
1121
+ };
1122
+ function moveLine(line, from, to) {
1123
+ const start = line.geometry.getAttribute("instanceStart");
1124
+ const array = start.data.array;
1125
+ array[0] = from.x;
1126
+ array[1] = from.y;
1127
+ array[2] = from.z;
1128
+ array[3] = to.x;
1129
+ array[4] = to.y;
1130
+ array[5] = to.z;
1131
+ start.data.needsUpdate = true;
1132
+ line.computeLineDistances();
1133
+ }
1134
+ function setLineColor(line, color) {
1135
+ const material = line.material;
1136
+ if (material.color.getHex() !== color) material.color.setHex(color);
1137
+ }
1138
+ function liveLabel(mode, placed, at, format) {
1139
+ if (mode === "distance" && placed.length === 1) {
1140
+ return format(distanceBetween(placed[0], at));
1141
+ }
1142
+ if (mode === "angle" && placed.length === 2) {
1143
+ return formatDegrees(angleAt(placed[1], placed[0], at));
1144
+ }
1145
+ return null;
1146
+ }
1147
+ function edgesBeside(object) {
1148
+ const siblings = object.parent?.children ?? [];
1149
+ for (const sibling of siblings) {
1150
+ if (!("isLineSegments" in sibling)) continue;
1151
+ const geometry = sibling.geometry;
1152
+ const positions = geometry.getAttribute("position");
1153
+ if (!positions) continue;
1154
+ return { positions: positions.array, matrixWorld: sibling.matrixWorld };
1155
+ }
1156
+ return null;
1157
+ }
1158
+ var MeasurementView = ({
1159
+ measurement,
1160
+ theme,
1161
+ format,
1162
+ showDeltas,
1163
+ label
1164
+ }) => {
1165
+ const text = measurementLabel(measurement, format);
1166
+ const anchor = measurementLabelAnchor(measurement);
1167
+ if (measurement.kind === "distance") {
1168
+ const [a2, b2] = measurement.points;
1169
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1170
+ /* @__PURE__ */ jsx4(Segment, { points: [a2, b2], color: theme.measure }),
1171
+ /* @__PURE__ */ jsx4(ScreenDot, { point: a2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1172
+ /* @__PURE__ */ jsx4(ScreenDot, { point: b2, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1173
+ showDeltas ? /* @__PURE__ */ jsx4(DeltaLegs, { a: a2, b: b2, id: measurement.id, format, label }) : null,
1174
+ /* @__PURE__ */ jsx4(Label, { at: anchor, text, kind: "distance", id: measurement.id, className: label })
1175
+ ] });
1176
+ }
1177
+ const [a, vertex, b] = measurement.points;
1178
+ const arc = angleArc(vertex, a, b, angleArcRadius(vertex, a, b));
1179
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1180
+ /* @__PURE__ */ jsx4(Segment, { points: [vertex, a], color: theme.measure }),
1181
+ /* @__PURE__ */ jsx4(Segment, { points: [vertex, b], color: theme.measure }),
1182
+ /* @__PURE__ */ jsx4(Polyline, { points: arc, color: theme.measure }),
1183
+ /* @__PURE__ */ jsx4(ScreenDot, { point: a, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1184
+ /* @__PURE__ */ jsx4(ScreenDot, { point: vertex, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1185
+ /* @__PURE__ */ jsx4(ScreenDot, { point: b, pixels: MEASURE_MARKER_PIXELS, color: theme.measure }),
1186
+ /* @__PURE__ */ jsx4(Label, { at: anchor, text, kind: "angle", id: measurement.id, className: label })
1187
+ ] });
1188
+ };
1189
+ var DeltaLegs = ({ a, b, id, format, label }) => {
1190
+ const camera = useThree4((state) => state.camera);
1191
+ const size = useThree4((state) => state.size);
1192
+ const invalidate = useThree4((state) => state.invalidate);
1193
+ const legs = useMemo4(() => deltaLegs(a, b), [a, b]);
1194
+ const lines = useRef3([]);
1195
+ const centre = useMemo4(() => {
1196
+ const at = midpoint(a, b);
1197
+ return new Vector35(at.x, at.y, at.z);
1198
+ }, [a, b]);
1199
+ useFrame2(() => {
1200
+ const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
1201
+ let changed = false;
1202
+ for (const line of lines.current) {
1203
+ const material = line?.material;
1204
+ if (!material || Math.abs(material.dashSize - dash) <= dash * 1e-3) continue;
1205
+ material.dashSize = dash;
1206
+ material.gapSize = dash;
1207
+ changed = true;
1208
+ }
1209
+ if (changed) invalidate();
1210
+ });
1211
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1212
+ legs.map((leg, index) => /* @__PURE__ */ jsx4(
1213
+ Line,
1214
+ {
1215
+ ref: (line) => {
1216
+ lines.current[index] = line;
1217
+ },
1218
+ points: [
1219
+ new Vector35(leg.from.x, leg.from.y, leg.from.z),
1220
+ new Vector35(leg.to.x, leg.to.y, leg.to.z)
1221
+ ],
1222
+ color: AXIS_COLORS[leg.axis],
1223
+ lineWidth: MEASURE_LINE_PIXELS.leg,
1224
+ dashed: true,
1225
+ dashSize: 1,
1226
+ gapSize: 1,
1227
+ depthTest: false,
1228
+ depthWrite: false,
1229
+ frustumCulled: false,
1230
+ renderOrder: MEASURE_RENDER_ORDER,
1231
+ raycast: () => null
1232
+ },
1233
+ leg.axis
1234
+ )),
1235
+ legs.map((leg) => /* @__PURE__ */ jsx4(
1236
+ Label,
1237
+ {
1238
+ at: midpoint(leg.from, leg.to),
1239
+ text: `${leg.axis.toUpperCase()} ${format(leg.length)}`,
1240
+ kind: "delta",
1241
+ axis: leg.axis,
1242
+ id,
1243
+ className: label
1244
+ },
1245
+ leg.axis
1246
+ ))
1247
+ ] });
1248
+ };
1249
+ 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 }) }) });
1250
+ var ScreenDot = ({ point, pixels, color }) => {
1251
+ const camera = useThree4((state) => state.camera);
1252
+ const size = useThree4((state) => state.size);
1253
+ const invalidate = useThree4((state) => state.invalidate);
1254
+ const node = useRef3(null);
1255
+ const dot = useDot();
1256
+ const position = useMemo4(() => new Vector35(point.x, point.y, point.z), [point]);
1257
+ useFrame2(() => {
1258
+ const group = node.current;
1259
+ if (!group) return;
1260
+ const length = screenLength(camera, position, size, pixels);
1261
+ if (Math.abs(group.scale.x - length) > length * 1e-3) {
1262
+ group.scale.setScalar(length);
1263
+ invalidate();
1264
+ }
1265
+ });
1266
+ 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 }) }) });
1267
+ };
1268
+ var Segment = ({ points, color }) => {
1269
+ const [a, b] = points;
1270
+ const ends = useMemo4(
1271
+ () => [new Vector35(a.x, a.y, a.z), new Vector35(b.x, b.y, b.z)],
1272
+ // Keyed on the numbers: a draft's tuple is a fresh array on every render,
1273
+ // and a rebuilt array would rebuild the line's geometry with it.
1274
+ [a.x, a.y, a.z, b.x, b.y, b.z]
1275
+ );
1276
+ return /* @__PURE__ */ jsx4(Polyline, { points: ends, color });
1277
+ };
1278
+ var Polyline = ({ points, color }) => {
1279
+ if (points.length < 2) return null;
1280
+ return /* @__PURE__ */ jsx4(
1281
+ Line,
1282
+ {
1283
+ points,
1284
+ color,
1285
+ lineWidth: MEASURE_LINE_PIXELS.line,
1286
+ depthTest: false,
1287
+ depthWrite: false,
1288
+ frustumCulled: false,
1289
+ renderOrder: MEASURE_RENDER_ORDER,
1290
+ raycast: () => null
1291
+ }
1292
+ );
1293
+ };
1294
+ function useDot() {
1295
+ const geometry = useMemo4(() => new SphereGeometry(1, 16, 12), []);
1296
+ useEffect3(() => () => geometry.dispose(), [geometry]);
1297
+ return geometry;
1298
+ }
1299
+ function isEditing(target) {
1300
+ if (!(target instanceof HTMLElement)) return false;
1301
+ return target.isContentEditable || target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement;
1302
+ }
1303
+ function className(extra) {
1304
+ return extra ? `${MEASURE_LABEL_CLASS} ${extra}` : MEASURE_LABEL_CLASS;
1305
+ }
1306
+
357
1307
  // src/model/normals.ts
358
1308
  function regionNormals(geometry, regions) {
359
1309
  const position = geometry.getAttribute("position");
@@ -388,14 +1338,23 @@ function regionNormals(geometry, regions) {
388
1338
  return normals;
389
1339
  }
390
1340
  export {
1341
+ ANGLE_ARC_FRACTION,
1342
+ ANGLE_ARC_SEGMENTS,
1343
+ AXES_PLANE_OFFSET,
1344
+ AXES_PLANE_SCALE,
1345
+ AXIS_COLORS,
391
1346
  Axes,
392
1347
  CAD_CAMERA_UP,
393
1348
  CANDIDATE_WEIGHT,
1349
+ CAP_HATCH,
394
1350
  CHAMFER,
395
1351
  CadCameraControls,
396
1352
  DEFAULT_FIT_MARGIN,
1353
+ DEFAULT_SECTION_NORMAL,
397
1354
  DEFAULT_THEME,
1355
+ DELTA_DASH_PIXELS,
398
1356
  DIRECTION_COLORS,
1357
+ DISABLED_SECTION,
399
1358
  DOUBLE_TAP_MS,
400
1359
  DirectionArrows,
401
1360
  EXCLUDE_FROM_FRAME,
@@ -408,7 +1367,12 @@ export {
408
1367
  HIGHLIGHT_WEIGHT,
409
1368
  HOVER_WEIGHT,
410
1369
  MAX_FRAME_RATIO,
1370
+ MEASURE_LABEL_CLASS,
1371
+ MEASURE_LINE_PIXELS,
1372
+ MEASURE_MARKER_PIXELS,
1373
+ MEASURE_RENDER_ORDER,
411
1374
  MIN_FRAME_RATIO,
1375
+ MeasureTool,
412
1376
  NO_MODIFIERS,
413
1377
  ORBIT_TARGET_COLOR,
414
1378
  ORBIT_TARGET_FADE_MS,
@@ -418,12 +1382,18 @@ export {
418
1382
  ORBIT_TARGET_RING_OPACITY,
419
1383
  ORBIT_TARGET_RING_PIXELS,
420
1384
  ORBIT_TARGET_RING_WIDTH,
1385
+ OUTLINE_SCALE,
421
1386
  PERSPECTIVE_FOV,
422
1387
  PICKED_SURFACE_LABEL,
1388
+ POINTS_PER_MEASUREMENT,
1389
+ PREVIEW_SCALE,
423
1390
  PartMesh,
424
1391
  PartReportFormatError,
425
1392
  REGION_ATTRIBUTE,
426
1393
  SECTION_RENDER_ORDER,
1394
+ SNAP_MARKER_PIXELS,
1395
+ SNAP_PIXELS,
1396
+ SectionTool,
427
1397
  SectionView,
428
1398
  TAP_SLOP,
429
1399
  UnsupportedKernelVersionError,
@@ -432,10 +1402,15 @@ export {
432
1402
  ViewCube,
433
1403
  Viewer,
434
1404
  adaptedUp,
1405
+ angleArc,
1406
+ angleArcRadius,
1407
+ angleAt,
1408
+ applyCapTheme,
435
1409
  applyHighlightLayers,
436
1410
  applyProjection,
437
1411
  arrowPlacement,
438
1412
  aspectRatio,
1413
+ axesPlanes,
439
1414
  bestOwner,
440
1415
  boundsFromBox,
441
1416
  buildPick,
@@ -445,25 +1420,43 @@ export {
445
1420
  cadViewDirections,
446
1421
  cameraLimits,
447
1422
  contentBounds,
1423
+ createCapMaterial,
1424
+ createMaskMaterial,
1425
+ createMaskTarget,
448
1426
  createPart,
1427
+ createSectionStore,
1428
+ createStencilMaterials,
449
1429
  cubeOutlineGeometry,
450
1430
  cubeZones,
451
1431
  currentViewDirection,
452
1432
  cycleOwner,
453
1433
  defaultBounds,
1434
+ deltaBetween,
1435
+ deltaLegs,
454
1436
  directionColor,
455
1437
  directionIndexOf,
456
1438
  directionLabel,
1439
+ distanceBetween,
457
1440
  dragPlane,
1441
+ excludedFromFrame,
458
1442
  featureTypeRank,
459
1443
  fitDistance,
460
1444
  focusForPick,
1445
+ formatDegrees,
1446
+ formatMillimetres,
461
1447
  gridGeometry,
462
1448
  gridSpec,
463
1449
  groupByDirection,
1450
+ hitUnderRay,
464
1451
  labelGeometry,
465
1452
  labelTexture,
1453
+ lockToAxis,
1454
+ measurementFromPoints,
1455
+ measurementLabel,
1456
+ measurementLabelAnchor,
1457
+ midpoint,
466
1458
  movedFar,
1459
+ nextMeasurementId,
467
1460
  normalizePartReport,
468
1461
  orbitTargetOpacity,
469
1462
  orthographicHalfHeight,
@@ -479,6 +1472,8 @@ export {
479
1472
  retargetPose,
480
1473
  sameDirection,
481
1474
  screenLength,
1475
+ screenPoint,
1476
+ sectionAnchor,
482
1477
  sectionBounds,
483
1478
  sectionConstant,
484
1479
  sectionDepth,
@@ -486,16 +1481,20 @@ export {
486
1481
  sectionDepthRange,
487
1482
  sectionFromPick,
488
1483
  sectionOffset,
1484
+ sectionOptionsFromState,
489
1485
  sectionPlane,
490
1486
  smoothRegionNormals,
1487
+ snapAt,
491
1488
  squaredUp,
492
1489
  startPosition,
1490
+ surfaceUnderRay,
493
1491
  targetBoundary,
494
1492
  themesEqual,
495
1493
  trackDoubleTaps,
496
1494
  trackTaps,
497
1495
  useContentBox,
498
1496
  useRetarget,
1497
+ useSectionStore,
499
1498
  useTapGuard,
500
1499
  useViewerControls,
501
1500
  viewDirection,