@toolpath/viewer 2.1.0 → 2.2.1

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/README.md CHANGED
@@ -357,9 +357,9 @@ individual buttons to use only the controls and order your application needs.
357
357
  </ViewerToolbarProvider>
358
358
  ```
359
359
 
360
- `ViewerToolbar.Controls`, `Divider`, and the fourteen `*Button` components are available on
360
+ `ViewerToolbar.Controls`, `Divider`, and the fifteen `*Button` components are available on
361
361
  `ViewerToolbar`: `StockButton`, `AxesButton`, `GridButton`, `BananaButton`, `ToolsButton`, `DirectionsButton`,
362
- `HoverButton`, `FocusButton`, `WireframeButton`, `SectionButton`, `MeasureButton`, `FitButton`,
362
+ `DfmButton` (shows or hides an application's DFM flags), `HoverButton`, `FocusButton`, `WireframeButton`, `SectionButton`, `MeasureButton`, `FitButton`,
363
363
  `ResetButton`, and `TopButton`. A rendered button requires its matching provider control. For an
364
364
  application-specific toolbar component, `useViewerToolbar()` reads the same controls from inside
365
365
  the provider.
@@ -1,13 +1,13 @@
1
1
  // src/engine/engine-part.tsx
2
- import { useEffect as useEffect7, useMemo as useMemo7 } from "react";
2
+ import { useEffect as useEffect8, useMemo as useMemo8 } from "react";
3
3
 
4
4
  // src/part-mesh.tsx
5
- import { useThree as useThree7 } from "@react-three/fiber";
5
+ import { useThree as useThree8 } from "@react-three/fiber";
6
6
  import {
7
7
  useCallback as useCallback3,
8
- useEffect as useEffect6,
9
- useLayoutEffect as useLayoutEffect2,
10
- useMemo as useMemo6,
8
+ useEffect as useEffect7,
9
+ useLayoutEffect as useLayoutEffect3,
10
+ useMemo as useMemo7,
11
11
  useRef as useRef6,
12
12
  useSyncExternalStore
13
13
  } from "react";
@@ -2526,10 +2526,10 @@ var Viewer = forwardRef(function Viewer2({
2526
2526
  });
2527
2527
 
2528
2528
  // src/section-view.tsx
2529
- import { useFrame as useFrame4, useThree as useThree5 } from "@react-three/fiber";
2530
- import { useCallback as useCallback2, useEffect as useEffect5, useLayoutEffect, useMemo as useMemo5, useRef as useRef4, useState } from "react";
2529
+ import { useFrame as useFrame4, useThree as useThree6 } from "@react-three/fiber";
2530
+ import { useCallback as useCallback2, useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useMemo as useMemo6, useRef as useRef4, useState } from "react";
2531
2531
  import {
2532
- Color as Color3,
2532
+ Color as Color4,
2533
2533
  DoubleSide as DoubleSide4,
2534
2534
  EdgesGeometry,
2535
2535
  Mesh as Mesh3,
@@ -2538,42 +2538,209 @@ import {
2538
2538
  Quaternion as Quaternion2,
2539
2539
  Raycaster as Raycaster2,
2540
2540
  Scene,
2541
- Vector2 as Vector24,
2541
+ Vector2 as Vector25,
2542
2542
  Vector3 as Vector310
2543
2543
  } from "three";
2544
2544
 
2545
- // src/render/directions.ts
2546
- import { Vector3 as Vector39 } from "three";
2547
- var CONE_AXIS = new Vector39(0, 1, 0);
2548
- var LENGTH = 0.45;
2549
- var HEAD = 0.45;
2550
- var HEAD_RADIUS = 0.3;
2551
- var SHAFT_RADIUS = 0.09;
2552
- var GAP = 0.2;
2553
- function arrowPlacement(direction, box) {
2554
- const center = box.getCenter(new Vector39());
2555
- const half = box.getSize(new Vector39()).multiplyScalar(0.5);
2556
- const radius = half.length() || 1;
2557
- const axis = new Vector39(direction.x, direction.y, direction.z);
2558
- if (axis.lengthSq() === 0) axis.set(0, 0, 1);
2559
- axis.normalize();
2560
- let exit = Number.POSITIVE_INFINITY;
2561
- for (const [component, extent] of [
2562
- [axis.x, half.x],
2563
- [axis.y, half.y],
2564
- [axis.z, half.z]
2565
- ]) {
2566
- if (Math.abs(component) > 1e-6) exit = Math.min(exit, extent / Math.abs(component));
2567
- }
2568
- if (!Number.isFinite(exit)) exit = radius;
2569
- return { tip: center.addScaledVector(axis, exit + radius * GAP), length: radius * LENGTH };
2545
+ // src/arrow-mesh.tsx
2546
+ import { useThree as useThree5 } from "@react-three/fiber";
2547
+ import { useEffect as useEffect5, useLayoutEffect, useMemo as useMemo5 } from "react";
2548
+
2549
+ // src/render/arrow-material.ts
2550
+ import { AlwaysDepth, Color as Color2, ShaderMaterial } from "three";
2551
+ var VERTEX = (
2552
+ /* glsl */
2553
+ `
2554
+ varying vec3 vNormal;
2555
+
2556
+ void main() {
2557
+ vNormal = normalize(normalMatrix * normal);
2558
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
2559
+ }
2560
+ `
2561
+ );
2562
+ var FRAGMENT = (
2563
+ /* glsl */
2564
+ `
2565
+ uniform vec3 uColor;
2566
+ uniform float uOpacity;
2567
+
2568
+ varying vec3 vNormal;
2569
+
2570
+ // Above, to the left and toward the camera, in the camera's own frame.
2571
+ const vec3 KEY = vec3(-0.4, 0.6, 0.7);
2572
+
2573
+ void main() {
2574
+ // Wrapped, and over a narrow range: a quarter darker at most, so the shape
2575
+ // shows without the colour going anywhere.
2576
+ float light = mix(0.75, 1.0, 0.5 + 0.5 * dot(normalize(vNormal), normalize(KEY)));
2577
+ gl_FragColor = vec4(uColor * light, uOpacity);
2578
+ #include <tonemapping_fragment>
2579
+ #include <colorspace_fragment>
2580
+ }
2581
+ `
2582
+ );
2583
+ function createArrowMaterial() {
2584
+ const uniforms = {
2585
+ uColor: { value: new Color2() },
2586
+ uOpacity: { value: 1 }
2587
+ };
2588
+ return new ShaderMaterial({
2589
+ vertexShader: VERTEX,
2590
+ fragmentShader: FRAGMENT,
2591
+ uniforms,
2592
+ transparent: true
2593
+ });
2594
+ }
2595
+ function createArrowDepthMaterial() {
2596
+ const material = createArrowMaterial();
2597
+ material.colorWrite = false;
2598
+ return material;
2599
+ }
2600
+ function applyArrowColor(material, color, opacity) {
2601
+ material.uniforms.uColor.value.set(color);
2602
+ material.uniforms.uOpacity.value = opacity;
2603
+ }
2604
+ var RESET_VERTEX = (
2605
+ /* glsl */
2606
+ `
2607
+ void main() {
2608
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
2609
+ // On the far plane, so what it leaves in the depth buffer is "nothing here".
2610
+ gl_Position.z = gl_Position.w;
2611
+ }
2612
+ `
2613
+ );
2614
+ var RESET_FRAGMENT = (
2615
+ /* glsl */
2616
+ `
2617
+ void main() {
2618
+ gl_FragColor = vec4(0.0);
2619
+ }
2620
+ `
2621
+ );
2622
+ function createDepthResetMaterial() {
2623
+ return new ShaderMaterial({
2624
+ vertexShader: RESET_VERTEX,
2625
+ fragmentShader: RESET_FRAGMENT,
2626
+ transparent: true,
2627
+ colorWrite: false,
2628
+ depthFunc: AlwaysDepth
2629
+ });
2630
+ }
2631
+
2632
+ // src/arrow-mesh.tsx
2633
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2634
+ var ArrowMesh = ({ geometry, color, opacity, onTop = false, events }) => {
2635
+ const invalidate = useThree5((state) => state.invalidate);
2636
+ const material = useMemo5(() => createArrowMaterial(), []);
2637
+ useEffect5(() => () => material.dispose(), [material]);
2638
+ const depth = useMemo5(() => createArrowDepthMaterial(), []);
2639
+ useEffect5(() => () => depth.dispose(), [depth]);
2640
+ const reset = useMemo5(() => onTop ? createDepthResetMaterial() : null, [onTop]);
2641
+ useEffect5(() => () => reset?.dispose(), [reset]);
2642
+ useLayoutEffect(() => {
2643
+ applyArrowColor(material, color, opacity);
2644
+ invalidate();
2645
+ }, [color, invalidate, material, opacity]);
2646
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
2647
+ reset ? /* @__PURE__ */ jsx4("mesh", { geometry, material: reset, renderOrder: 0, raycast: () => null }) : null,
2648
+ /* @__PURE__ */ jsx4("mesh", { geometry, material: depth, renderOrder: 1, raycast: () => null }),
2649
+ /* @__PURE__ */ jsx4("mesh", { geometry, material, renderOrder: 2, ...events })
2650
+ ] });
2651
+ };
2652
+
2653
+ // src/render/arrow.ts
2654
+ import { LatheGeometry, Vector2 as Vector23, Vector3 as Vector39 } from "three";
2655
+ var ARROW_AXIS2 = new Vector39(0, 1, 0);
2656
+ var ARROW_ROUND = 32;
2657
+ var HEAD_LENGTH = 0.415;
2658
+ var HEAD_RADIUS = 0.153;
2659
+ var SHAFT_RADIUS = 0.0515;
2660
+ var HANDLE_TIP = 0.76;
2661
+ var HANDLE_HEAD_LENGTH = 0.3;
2662
+ var HANDLE_HEAD_RADIUS = 0.14;
2663
+ var HANDLE_SHAFT_RADIUS = 0.035;
2664
+ var HANDLE_BEAD_RADIUS = 0.1;
2665
+ var RIM_ROUND = 6e-3;
2666
+ var NECK_ROUND = 8e-3;
2667
+ var BEAD_ROUND = 0.02;
2668
+ var ROUND_STEPS = 4;
2669
+ var CAP_STEPS = 6;
2670
+ var BEAD_STEPS = 12;
2671
+ var head = ({ length, radius, shaft }) => [
2672
+ { at: new Vector23(0, 0) },
2673
+ { at: new Vector23(radius, length), round: RIM_ROUND },
2674
+ { at: new Vector23(shaft, length), round: NECK_ROUND }
2675
+ ];
2676
+ var rounded = (corners) => corners.flatMap(({ at, round = 0 }, index) => {
2677
+ const before = corners[index - 1]?.at;
2678
+ const after = corners[index + 1]?.at;
2679
+ if (!(round > 0) || !before || !after) return [at];
2680
+ const back = before.clone().sub(at);
2681
+ const ahead = after.clone().sub(at);
2682
+ const reach = Math.min(back.length(), ahead.length()) / 2;
2683
+ back.normalize();
2684
+ ahead.normalize();
2685
+ const angle = Math.acos(Math.min(Math.max(back.dot(ahead), -1), 1));
2686
+ if (angle > Math.PI - 1e-6 || angle < 1e-6) return [at];
2687
+ const half = angle / 2;
2688
+ const offset = Math.min(round / Math.tan(half), reach);
2689
+ const radius = offset * Math.tan(half);
2690
+ const centre = at.clone().addScaledVector(back.clone().add(ahead).normalize(), radius / Math.sin(half));
2691
+ const from = at.clone().addScaledVector(back, offset).sub(centre);
2692
+ const to = at.clone().addScaledVector(ahead, offset).sub(centre);
2693
+ const start = Math.atan2(from.y, from.x);
2694
+ let sweep = Math.atan2(to.y, to.x) - start;
2695
+ if (sweep > Math.PI) sweep -= Math.PI * 2;
2696
+ if (sweep < -Math.PI) sweep += Math.PI * 2;
2697
+ return Array.from({ length: ROUND_STEPS + 1 }, (_, step) => {
2698
+ const turn = start + sweep * step / ROUND_STEPS;
2699
+ return new Vector23(centre.x + Math.cos(turn) * radius, centre.y + Math.sin(turn) * radius);
2700
+ });
2701
+ });
2702
+ function arrowProfile() {
2703
+ const body = rounded([
2704
+ ...head({ length: HEAD_LENGTH, radius: HEAD_RADIUS, shaft: SHAFT_RADIUS }),
2705
+ { at: new Vector23(SHAFT_RADIUS, 1 - SHAFT_RADIUS) }
2706
+ ]);
2707
+ const tail = Array.from({ length: CAP_STEPS }, (_, step) => {
2708
+ const turn = (step + 1) / CAP_STEPS * (Math.PI / 2);
2709
+ return new Vector23(
2710
+ step + 1 === CAP_STEPS ? 0 : Math.cos(turn) * SHAFT_RADIUS,
2711
+ 1 - SHAFT_RADIUS + Math.sin(turn) * SHAFT_RADIUS
2712
+ );
2713
+ });
2714
+ return [...body, ...tail];
2715
+ }
2716
+ function handleProfile() {
2717
+ const lower = head({
2718
+ length: HANDLE_HEAD_LENGTH,
2719
+ radius: HANDLE_HEAD_RADIUS,
2720
+ shaft: HANDLE_SHAFT_RADIUS
2721
+ }).map(({ at, round }) => ({ at: new Vector23(at.x, at.y - HANDLE_TIP), round }));
2722
+ const upper = lower.slice().reverse().map(({ at, round }) => ({ at: new Vector23(at.x, -at.y), round }));
2723
+ const meet = Math.acos(HANDLE_SHAFT_RADIUS / HANDLE_BEAD_RADIUS);
2724
+ const bead = Array.from({ length: BEAD_STEPS + 1 }, (_, step) => {
2725
+ const turn = -meet + 2 * meet * step / BEAD_STEPS;
2726
+ const at = new Vector23(Math.cos(turn), Math.sin(turn)).multiplyScalar(HANDLE_BEAD_RADIUS);
2727
+ return { at, round: step === 0 || step === BEAD_STEPS ? BEAD_ROUND : 0 };
2728
+ });
2729
+ return rounded([...lower, ...bead, ...upper]);
2570
2730
  }
2731
+ var turned = (profile) => {
2732
+ const geometry = new LatheGeometry(profile, ARROW_ROUND);
2733
+ geometry.normalizeNormals();
2734
+ return geometry;
2735
+ };
2736
+ var arrowGeometry = () => turned(arrowProfile());
2737
+ var handleGeometry = () => turned(handleProfile());
2571
2738
 
2572
2739
  // src/render/section-cap.ts
2573
2740
  import {
2574
2741
  AlwaysStencilFunc,
2575
2742
  BackSide,
2576
- Color as Color2,
2743
+ Color as Color3,
2577
2744
  DecrementWrapStencilOp,
2578
2745
  DoubleSide as DoubleSide3,
2579
2746
  FrontSide,
@@ -2582,11 +2749,11 @@ import {
2582
2749
  MeshBasicMaterial as MeshBasicMaterial2,
2583
2750
  NotEqualStencilFunc,
2584
2751
  ReplaceStencilOp,
2585
- ShaderMaterial,
2586
- Vector2 as Vector23,
2752
+ ShaderMaterial as ShaderMaterial2,
2753
+ Vector2 as Vector24,
2587
2754
  WebGLRenderTarget
2588
2755
  } from "three";
2589
- var VERTEX = (
2756
+ var VERTEX2 = (
2590
2757
  /* glsl */
2591
2758
  `
2592
2759
  void main() {
@@ -2594,7 +2761,7 @@ void main() {
2594
2761
  }
2595
2762
  `
2596
2763
  );
2597
- var FRAGMENT = (
2764
+ var FRAGMENT2 = (
2598
2765
  /* glsl */
2599
2766
  `
2600
2767
  uniform sampler2D uMask;
@@ -2642,17 +2809,17 @@ function createMaskTarget() {
2642
2809
  function createCapMaterial(mask) {
2643
2810
  const uniforms = {
2644
2811
  uMask: { value: mask },
2645
- uResolution: { value: new Vector23(1, 1) },
2646
- uFill: { value: new Color2() },
2647
- uHatch: { value: new Color2() },
2648
- uOutline: { value: new Color2() },
2812
+ uResolution: { value: new Vector24(1, 1) },
2813
+ uFill: { value: new Color3() },
2814
+ uHatch: { value: new Color3() },
2815
+ uOutline: { value: new Color3() },
2649
2816
  uPitch: { value: CAP_HATCH.pitch },
2650
2817
  uLine: { value: CAP_HATCH.line },
2651
2818
  uThickness: { value: CAP_HATCH.outline }
2652
2819
  };
2653
- return new ShaderMaterial({
2654
- vertexShader: VERTEX,
2655
- fragmentShader: FRAGMENT,
2820
+ return new ShaderMaterial2({
2821
+ vertexShader: VERTEX2,
2822
+ fragmentShader: FRAGMENT2,
2656
2823
  uniforms,
2657
2824
  side: DoubleSide3,
2658
2825
  stencilWrite: true,
@@ -2711,7 +2878,7 @@ function createMaskMaterial() {
2711
2878
  }
2712
2879
 
2713
2880
  // src/section-view.tsx
2714
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2881
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2715
2882
  var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
2716
2883
  function resolveSectionPlane(options, box) {
2717
2884
  if (!options?.enabled || box.isEmpty()) return null;
@@ -2747,43 +2914,45 @@ var SectionView = ({
2747
2914
  showHandle,
2748
2915
  onDrag
2749
2916
  }) => {
2750
- const camera = useThree5((state) => state.camera);
2751
- const size = useThree5((state) => state.size);
2752
- const dpr = useThree5((state) => state.viewport.dpr);
2753
- const gl = useThree5((state) => state.gl);
2754
- const invalidate = useThree5((state) => state.invalidate);
2755
- const controls = useThree5((state) => state.controls);
2756
- const domElement = useThree5((state) => state.gl.domElement);
2917
+ const camera = useThree6((state) => state.camera);
2918
+ const size = useThree6((state) => state.size);
2919
+ const dpr = useThree6((state) => state.viewport.dpr);
2920
+ const gl = useThree6((state) => state.gl);
2921
+ const invalidate = useThree6((state) => state.invalidate);
2922
+ const controls = useThree6((state) => state.controls);
2923
+ const domElement = useThree6((state) => state.gl.domElement);
2757
2924
  const handleRef = useRef4(null);
2758
2925
  const [hovered, setHovered] = useState(false);
2759
2926
  const dragging = useRef4(null);
2760
- const span = useMemo5(() => box.getSize(new Vector310()).length(), [box]);
2927
+ const span = useMemo6(() => box.getSize(new Vector310()).length(), [box]);
2761
2928
  const frameSize = span * SECTION_GIZMO_FRAME_SCALE;
2762
- const directionColor2 = useMemo5(() => sectionDirectionColor(plane.normal), [plane]);
2929
+ const directionColor2 = useMemo6(() => sectionDirectionColor(plane.normal), [plane]);
2763
2930
  const resolvedHandleColor = sectionHandleColor(handleColor, directionColor2);
2764
- const clip = useMemo5(() => [plane], [plane]);
2765
- const capPosition = useMemo5(() => sectionAnchor(box, plane), [box, plane]);
2766
- const capQuaternion = useMemo5(() => new Quaternion2().setFromUnitVectors(Z, plane.normal), [plane]);
2767
- const handleQuaternion = useMemo5(
2768
- () => new Quaternion2().setFromUnitVectors(CONE_AXIS, plane.normal.clone().negate()),
2931
+ const clip = useMemo6(() => [plane], [plane]);
2932
+ const capPosition = useMemo6(() => sectionAnchor(box, plane), [box, plane]);
2933
+ const capQuaternion = useMemo6(() => new Quaternion2().setFromUnitVectors(Z, plane.normal), [plane]);
2934
+ const handleQuaternion = useMemo6(
2935
+ () => new Quaternion2().setFromUnitVectors(ARROW_AXIS2, plane.normal.clone().negate()),
2769
2936
  [plane]
2770
2937
  );
2771
- const frameSurface = useMemo5(() => new PlaneGeometry(frameSize, frameSize), [frameSize]);
2772
- useEffect5(() => () => frameSurface.dispose(), [frameSurface]);
2773
- const frameEdges = useMemo5(() => new EdgesGeometry(frameSurface), [frameSurface]);
2774
- useEffect5(() => () => frameEdges.dispose(), [frameEdges]);
2775
- const stencils = useMemo5(() => createStencilMaterials([]), []);
2776
- useEffect5(() => () => stencils.forEach((material) => material.dispose()), [stencils]);
2777
- useLayoutEffect(() => {
2938
+ const frameSurface = useMemo6(() => new PlaneGeometry(frameSize, frameSize), [frameSize]);
2939
+ useEffect6(() => () => frameSurface.dispose(), [frameSurface]);
2940
+ const frameEdges = useMemo6(() => new EdgesGeometry(frameSurface), [frameSurface]);
2941
+ useEffect6(() => () => frameEdges.dispose(), [frameEdges]);
2942
+ const handleShape = useMemo6(() => handleGeometry(), []);
2943
+ useEffect6(() => () => handleShape.dispose(), [handleShape]);
2944
+ const stencils = useMemo6(() => createStencilMaterials([]), []);
2945
+ useEffect6(() => () => stencils.forEach((material) => material.dispose()), [stencils]);
2946
+ useLayoutEffect2(() => {
2778
2947
  for (const material of stencils) material.clippingPlanes = clip;
2779
2948
  }, [clip, stencils]);
2780
- const mask = useMemo5(() => createMaskTarget(), []);
2781
- useEffect5(() => () => mask.dispose(), [mask]);
2782
- const cap = useMemo5(() => createCapMaterial(mask.texture), [mask]);
2783
- useEffect5(() => () => cap.dispose(), [cap]);
2784
- const quad = useMemo5(() => new PlaneGeometry(span * 1.5, span * 1.5), [span]);
2785
- useEffect5(() => () => quad.dispose(), [quad]);
2786
- const maskScene = useMemo5(() => {
2949
+ const mask = useMemo6(() => createMaskTarget(), []);
2950
+ useEffect6(() => () => mask.dispose(), [mask]);
2951
+ const cap = useMemo6(() => createCapMaterial(mask.texture), [mask]);
2952
+ useEffect6(() => () => cap.dispose(), [cap]);
2953
+ const quad = useMemo6(() => new PlaneGeometry(span * 1.5, span * 1.5), [span]);
2954
+ useEffect6(() => () => quad.dispose(), [quad]);
2955
+ const maskScene = useMemo6(() => {
2787
2956
  const scene = new Scene();
2788
2957
  const [back, front] = stencils;
2789
2958
  const backMesh = new Mesh3(geometry, back);
@@ -2795,19 +2964,19 @@ var SectionView = ({
2795
2964
  scene.add(backMesh, frontMesh, maskMesh);
2796
2965
  return { scene, maskMesh };
2797
2966
  }, [geometry, quad, stencils]);
2798
- useEffect5(() => () => maskScene.maskMesh.material.dispose(), [maskScene]);
2799
- useEffect5(() => {
2967
+ useEffect6(() => () => maskScene.maskMesh.material.dispose(), [maskScene]);
2968
+ useEffect6(() => {
2800
2969
  maskScene.maskMesh.position.copy(capPosition);
2801
2970
  maskScene.maskMesh.quaternion.copy(capQuaternion);
2802
2971
  }, [capPosition, capQuaternion, maskScene]);
2803
- useLayoutEffect(() => {
2804
- const buffer = gl.getDrawingBufferSize(new Vector24());
2972
+ useLayoutEffect2(() => {
2973
+ const buffer = gl.getDrawingBufferSize(new Vector25());
2805
2974
  mask.setSize(buffer.x, buffer.y);
2806
2975
  cap.uniforms.uResolution.value.copy(buffer);
2807
2976
  applyCapTheme(cap, theme, dpr);
2808
2977
  invalidate();
2809
2978
  }, [cap, dpr, gl, invalidate, mask, size, theme]);
2810
- const clearColor = useMemo5(() => new Color3(), []);
2979
+ const clearColor = useMemo6(() => new Color4(), []);
2811
2980
  useFrame4(({ gl: renderer, camera: eye }) => {
2812
2981
  const previousAlpha = renderer.getClearAlpha();
2813
2982
  renderer.getClearColor(clearColor);
@@ -2848,7 +3017,7 @@ var SectionView = ({
2848
3017
  dragging.current = start;
2849
3018
  setControlsEnabled(false);
2850
3019
  const raycaster = new Raycaster2();
2851
- const pointer = new Vector24();
3020
+ const pointer = new Vector25();
2852
3021
  const move = (native) => {
2853
3022
  const rect = domElement.getBoundingClientRect();
2854
3023
  pointer.set(
@@ -2888,8 +3057,8 @@ var SectionView = ({
2888
3057
  // Excluded from framing, all of it. The cap is a quad half again as wide as
2889
3058
  // the part's diagonal, so a Fit that measured it would frame the cut rather
2890
3059
  // than the part and leave the part a speck in the middle of it.
2891
- /* @__PURE__ */ jsxs3("group", { userData: FURNITURE2, children: [
2892
- /* @__PURE__ */ jsx4(
3060
+ /* @__PURE__ */ jsxs4("group", { userData: FURNITURE2, children: [
3061
+ /* @__PURE__ */ jsx5(
2893
3062
  "mesh",
2894
3063
  {
2895
3064
  geometry,
@@ -2898,7 +3067,7 @@ var SectionView = ({
2898
3067
  raycast: () => null
2899
3068
  }
2900
3069
  ),
2901
- /* @__PURE__ */ jsx4(
3070
+ /* @__PURE__ */ jsx5(
2902
3071
  "mesh",
2903
3072
  {
2904
3073
  geometry,
@@ -2907,7 +3076,7 @@ var SectionView = ({
2907
3076
  raycast: () => null
2908
3077
  }
2909
3078
  ),
2910
- /* @__PURE__ */ jsx4(
3079
+ /* @__PURE__ */ jsx5(
2911
3080
  "mesh",
2912
3081
  {
2913
3082
  geometry: quad,
@@ -2918,15 +3087,15 @@ var SectionView = ({
2918
3087
  raycast: () => null
2919
3088
  }
2920
3089
  ),
2921
- showHandle && onDrag ? /* @__PURE__ */ jsxs3(Fragment, { children: [
2922
- /* @__PURE__ */ jsxs3(
3090
+ showHandle && onDrag ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
3091
+ /* @__PURE__ */ jsxs4(
2923
3092
  "group",
2924
3093
  {
2925
3094
  position: capPosition,
2926
3095
  quaternion: capQuaternion,
2927
3096
  renderOrder: SECTION_RENDER_ORDER.outline,
2928
3097
  children: [
2929
- /* @__PURE__ */ jsx4("mesh", { geometry: frameSurface, raycast: () => null, children: /* @__PURE__ */ jsx4(
3098
+ /* @__PURE__ */ jsx5("mesh", { geometry: frameSurface, raycast: () => null, children: /* @__PURE__ */ jsx5(
2930
3099
  "meshBasicMaterial",
2931
3100
  {
2932
3101
  color: directionColor2,
@@ -2937,59 +3106,27 @@ var SectionView = ({
2937
3106
  depthWrite: false
2938
3107
  }
2939
3108
  ) }),
2940
- /* @__PURE__ */ jsx4("lineSegments", { geometry: frameEdges, raycast: () => null, children: /* @__PURE__ */ jsx4("lineBasicMaterial", { color: directionColor2, depthTest: false, depthWrite: false }) })
3109
+ /* @__PURE__ */ jsx5("lineSegments", { geometry: frameEdges, raycast: () => null, children: /* @__PURE__ */ jsx5("lineBasicMaterial", { color: directionColor2, depthTest: false, depthWrite: false }) })
2941
3110
  ]
2942
3111
  }
2943
3112
  ),
2944
- /* @__PURE__ */ jsxs3(
3113
+ /* @__PURE__ */ jsx5(
2945
3114
  "group",
2946
3115
  {
2947
3116
  ref: handleRef,
2948
3117
  position: capPosition,
2949
3118
  quaternion: handleQuaternion,
2950
3119
  renderOrder: SECTION_RENDER_ORDER.handle,
2951
- children: [
2952
- /* @__PURE__ */ jsxs3("mesh", { ...grab, children: [
2953
- /* @__PURE__ */ jsx4("sphereGeometry", { args: [0.1, 16, 12] }),
2954
- /* @__PURE__ */ jsx4(
2955
- "meshBasicMaterial",
2956
- {
2957
- color: hovered ? theme.hover : resolvedHandleColor,
2958
- depthTest: false
2959
- }
2960
- )
2961
- ] }),
2962
- /* @__PURE__ */ jsxs3("mesh", { ...grab, children: [
2963
- /* @__PURE__ */ jsx4("cylinderGeometry", { args: [0.035, 0.035, 0.9, 12] }),
2964
- /* @__PURE__ */ jsx4(
2965
- "meshBasicMaterial",
2966
- {
2967
- color: hovered ? theme.hover : resolvedHandleColor,
2968
- depthTest: false
2969
- }
2970
- )
2971
- ] }),
2972
- /* @__PURE__ */ jsxs3("mesh", { position: [0, 0.61, 0], ...grab, children: [
2973
- /* @__PURE__ */ jsx4("coneGeometry", { args: [0.15, 0.3, 20] }),
2974
- /* @__PURE__ */ jsx4(
2975
- "meshBasicMaterial",
2976
- {
2977
- color: hovered ? theme.hover : resolvedHandleColor,
2978
- depthTest: false
2979
- }
2980
- )
2981
- ] }),
2982
- /* @__PURE__ */ jsxs3("mesh", { position: [0, -0.61, 0], rotation: [Math.PI, 0, 0], ...grab, children: [
2983
- /* @__PURE__ */ jsx4("coneGeometry", { args: [0.15, 0.3, 20] }),
2984
- /* @__PURE__ */ jsx4(
2985
- "meshBasicMaterial",
2986
- {
2987
- color: hovered ? theme.hover : resolvedHandleColor,
2988
- depthTest: false
2989
- }
2990
- )
2991
- ] })
2992
- ]
3120
+ children: /* @__PURE__ */ jsx5(
3121
+ ArrowMesh,
3122
+ {
3123
+ geometry: handleShape,
3124
+ color: hovered ? theme.hover : resolvedHandleColor,
3125
+ opacity: 1,
3126
+ onTop: true,
3127
+ events: grab
3128
+ }
3129
+ )
2993
3130
  }
2994
3131
  )
2995
3132
  ] }) : null
@@ -2998,11 +3135,11 @@ var SectionView = ({
2998
3135
  };
2999
3136
 
3000
3137
  // src/content-box.ts
3001
- import { useFrame as useFrame5, useThree as useThree6 } from "@react-three/fiber";
3138
+ import { useFrame as useFrame5, useThree as useThree7 } from "@react-three/fiber";
3002
3139
  import { useRef as useRef5, useState as useState2 } from "react";
3003
3140
  import { Box3 as Box34 } from "three";
3004
3141
  function useContentBox() {
3005
- const scene = useThree6((state) => state.scene);
3142
+ const scene = useThree7((state) => state.scene);
3006
3143
  const [box, setBox] = useState2(() => new Box34());
3007
3144
  const measured = useRef5(false);
3008
3145
  useFrame5(() => {
@@ -3017,7 +3154,7 @@ function useContentBox() {
3017
3154
  }
3018
3155
 
3019
3156
  // src/part-mesh.tsx
3020
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3157
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3021
3158
  var PartMesh = ({
3022
3159
  model,
3023
3160
  geometry,
@@ -3041,13 +3178,13 @@ var PartMesh = ({
3041
3178
  display = "solid",
3042
3179
  children
3043
3180
  }) => {
3044
- const { camera, controls, invalidate } = useThree7();
3045
- const context = useMemo6(() => ({ model, geometry }), [model, geometry]);
3181
+ const { camera, controls, invalidate } = useThree8();
3182
+ const context = useMemo7(() => ({ model, geometry }), [model, geometry]);
3046
3183
  const viewerControls = useViewerControls();
3047
3184
  const resolved = useStableTheme(theme);
3048
3185
  const currentTheme = useRef6(resolved);
3049
3186
  currentTheme.current = resolved;
3050
- const part = useMemo6(() => createPart(model, geometry, currentTheme.current), [geometry, model]);
3187
+ const part = useMemo7(() => createPart(model, geometry, currentTheme.current), [geometry, model]);
3051
3188
  const hoverRegion = useRef6(null);
3052
3189
  const onHoverRef = useRef6(onHover);
3053
3190
  onHoverRef.current = onHover;
@@ -3056,7 +3193,7 @@ var PartMesh = ({
3056
3193
  const shared = useSyncExternalStore(store.subscribe, store.get, store.get);
3057
3194
  const controlled = section !== void 0;
3058
3195
  const options = controlled ? section : shared ?? void 0;
3059
- const cut = useMemo6(() => resolveSectionPlane(options, box), [box, options]);
3196
+ const cut = useMemo7(() => resolveSectionPlane(options, box), [box, options]);
3060
3197
  const engaged = useSyncExternalStore(store.subscribe, store.isEngaged, store.isEngaged);
3061
3198
  const pressedWhileEngaged = useRef6(false);
3062
3199
  const layers = useRef6({
@@ -3084,19 +3221,19 @@ var PartMesh = ({
3084
3221
  );
3085
3222
  invalidate();
3086
3223
  }, [invalidate, part]);
3087
- useEffect6(() => () => part.dispose(), [part]);
3088
- const adjacency = useMemo6(() => regionAdjacency(model, geometry), [model, geometry]);
3089
- useEffect6(() => {
3224
+ useEffect7(() => () => part.dispose(), [part]);
3225
+ const adjacency = useMemo7(() => regionAdjacency(model, geometry), [model, geometry]);
3226
+ useEffect7(() => {
3090
3227
  onAdjacency?.(adjacency);
3091
3228
  }, [adjacency, onAdjacency]);
3092
3229
  const framed = useRef6(null);
3093
- useEffect6(() => {
3230
+ useEffect7(() => {
3094
3231
  if (focusFeature === null || focusFeature === framed.current) return;
3095
3232
  framed.current = focusFeature;
3096
3233
  const box2 = part.boxForFeature(focusFeature);
3097
3234
  if (box2) viewerControls.frameBox(box2);
3098
3235
  }, [focusFeature, part, viewerControls]);
3099
- useLayoutEffect2(() => {
3236
+ useLayoutEffect3(() => {
3100
3237
  part.setTheme(resolved);
3101
3238
  repaint();
3102
3239
  }, [part, repaint, resolved]);
@@ -3105,20 +3242,20 @@ var PartMesh = ({
3105
3242
  const selectionRef = useRef6(selection);
3106
3243
  selectionRef.current = selection;
3107
3244
  const focusKey = focusStateKey(focus, selection);
3108
- useLayoutEffect2(() => {
3245
+ useLayoutEffect3(() => {
3109
3246
  part.setFocus(selectionRef.current, focusRef.current);
3110
3247
  invalidate();
3111
3248
  }, [focusKey, invalidate, part]);
3112
- useLayoutEffect2(() => {
3249
+ useLayoutEffect3(() => {
3113
3250
  part.setDisplay(display, showEdges);
3114
3251
  invalidate();
3115
3252
  }, [display, invalidate, part, showEdges]);
3116
- useLayoutEffect2(() => {
3253
+ useLayoutEffect3(() => {
3117
3254
  part.setClippingPlanes(cut ? [cut.plane] : null);
3118
3255
  invalidate();
3119
3256
  }, [cut, invalidate, part]);
3120
3257
  const reportedSection = useRef6("");
3121
- useLayoutEffect2(() => {
3258
+ useLayoutEffect3(() => {
3122
3259
  const state = cut?.state ?? null;
3123
3260
  const key = state ? `${state.constant}|${state.normal.x},${state.normal.y},${state.normal.z}` : "";
3124
3261
  if (key === reportedSection.current) return;
@@ -3135,12 +3272,12 @@ var PartMesh = ({
3135
3272
  highlights.map((entry) => `${entry.tag}:${entry.color}:${entry.weight ?? ""}`).join(" "),
3136
3273
  regionHighlights.map((entry) => `${entry.region}:${entry.color}:${entry.weight ?? ""}`).join(" ")
3137
3274
  ].join("|");
3138
- useLayoutEffect2(() => {
3275
+ useLayoutEffect3(() => {
3139
3276
  repaint();
3140
3277
  }, [layerKey, repaint]);
3141
3278
  const isTap = useTapGuard();
3142
3279
  const retarget = useRetarget();
3143
- const doubles = useMemo6(() => trackDoubleTaps(), []);
3280
+ const doubles = useMemo7(() => trackDoubleTaps(), []);
3144
3281
  const pickFor = (event, doubled = false) => {
3145
3282
  const triangleIndex = event.faceIndex;
3146
3283
  if (triangleIndex == null) return null;
@@ -3175,13 +3312,13 @@ var PartMesh = ({
3175
3312
  repaint();
3176
3313
  onHover?.(next);
3177
3314
  };
3178
- useLayoutEffect2(() => {
3315
+ useLayoutEffect3(() => {
3179
3316
  if (hover || hoverRegion.current === null) return;
3180
3317
  hoverRegion.current = null;
3181
3318
  repaint();
3182
3319
  onHoverRef.current?.(null);
3183
3320
  }, [hover, repaint]);
3184
- useLayoutEffect2(() => {
3321
+ useLayoutEffect3(() => {
3185
3322
  if (!engaged || hoverRegion.current === null) return;
3186
3323
  hoverRegion.current = null;
3187
3324
  repaint();
@@ -3205,8 +3342,8 @@ var PartMesh = ({
3205
3342
  },
3206
3343
  [box, controlled, cut, onSectionChange, options, store]
3207
3344
  );
3208
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
3209
- /* @__PURE__ */ jsx5(
3345
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3346
+ /* @__PURE__ */ jsx6(
3210
3347
  "primitive",
3211
3348
  {
3212
3349
  object: part.object,
@@ -3243,7 +3380,7 @@ var PartMesh = ({
3243
3380
  }
3244
3381
  }
3245
3382
  ),
3246
- cut ? /* @__PURE__ */ jsx5(
3383
+ cut ? /* @__PURE__ */ jsx6(
3247
3384
  SectionView,
3248
3385
  {
3249
3386
  geometry,
@@ -3255,7 +3392,7 @@ var PartMesh = ({
3255
3392
  onDrag: !controlled || onSectionChange ? dragSection : void 0
3256
3393
  }
3257
3394
  ) : null,
3258
- children ? /* @__PURE__ */ jsx5(PartContext.Provider, { value: context, children }) : null
3395
+ children ? /* @__PURE__ */ jsx6(PartContext.Provider, { value: context, children }) : null
3259
3396
  ] });
3260
3397
  };
3261
3398
  var ORIGIN = new Vector311();
@@ -3884,15 +4021,15 @@ function compareVersions(a, b) {
3884
4021
  }
3885
4022
 
3886
4023
  // src/engine/engine-part.tsx
3887
- import { jsx as jsx6 } from "react/jsx-runtime";
4024
+ import { jsx as jsx7 } from "react/jsx-runtime";
3888
4025
  var EnginePart = ({ report, ...props }) => {
3889
- const model = useMemo7(() => normalizePartReport(report), [report]);
4026
+ const model = useMemo8(() => normalizePartReport(report), [report]);
3890
4027
  const resource = useGeometryResource(model);
3891
- useEffect7(() => {
4028
+ useEffect8(() => {
3892
4029
  engineGeometryCache.retain(resource);
3893
4030
  return () => engineGeometryCache.release(resource);
3894
4031
  }, [resource]);
3895
- return /* @__PURE__ */ jsx6(PartMesh, { model, geometry: resource.geometry, ...props });
4032
+ return /* @__PURE__ */ jsx7(PartMesh, { model, geometry: resource.geometry, ...props });
3896
4033
  };
3897
4034
  function useGeometryResource(part) {
3898
4035
  const resource = engineGeometryCache.get(part);
@@ -4045,11 +4182,9 @@ export {
4045
4182
  useSectionStore,
4046
4183
  useRetarget,
4047
4184
  Viewer,
4048
- CONE_AXIS,
4049
- HEAD,
4050
- HEAD_RADIUS,
4051
- SHAFT_RADIUS,
4052
- arrowPlacement,
4185
+ ArrowMesh,
4186
+ ARROW_AXIS2 as ARROW_AXIS,
4187
+ arrowGeometry,
4053
4188
  createMaskTarget,
4054
4189
  createCapMaterial,
4055
4190
  applyCapTheme,
@@ -12,7 +12,7 @@ import {
12
12
  parsePartGeometry,
13
13
  partMeshAssets,
14
14
  smoothRegionNormals
15
- } from "../chunk-WTTOUG4P.js";
15
+ } from "../chunk-7S6SIYHL.js";
16
16
  export {
17
17
  EnginePart,
18
18
  MIN_KERNEL_VERSION,
package/dist/index.d.ts CHANGED
@@ -90,6 +90,7 @@ interface ViewerToolbarControls {
90
90
  /** Tools drawn on the part: see `<ToolMarks>`. */
91
91
  tools?: ViewerToolbarControl;
92
92
  directions?: ViewerToolbarControl;
93
+ dfm?: ViewerToolbarControl;
93
94
  hover?: ViewerToolbarControl;
94
95
  focus?: ViewerToolbarControl;
95
96
  wireframe?: ViewerToolbarControl;
@@ -131,6 +132,7 @@ declare const ViewerToolbar: (({ className, children }: ViewerToolbarProps) => r
131
132
  BananaButton: () => react.JSX.Element;
132
133
  ToolsButton: () => react.JSX.Element;
133
134
  DirectionsButton: () => react.JSX.Element;
135
+ DfmButton: () => react.JSX.Element;
134
136
  HoverButton: () => react.JSX.Element;
135
137
  FocusButton: () => react.JSX.Element;
136
138
  WireframeButton: () => react.JSX.Element;
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  ANGLE_ARC_FRACTION,
3
3
  ANGLE_ARC_SEGMENTS,
4
+ ARROW_AXIS,
4
5
  AXES_PLANE_OFFSET,
5
6
  AXES_PLANE_SCALE,
6
7
  AXIS_COLORS,
8
+ ArrowMesh,
7
9
  CAD_CAMERA_UP,
8
10
  CANDIDATE_WEIGHT,
9
11
  CAP_HATCH,
10
12
  CHAMFER,
11
- CONE_AXIS,
12
13
  CONTROL_SCHEME_OPTIONS,
13
14
  CadCameraControls,
14
15
  DEFAULT_FIT_MARGIN,
@@ -23,8 +24,6 @@ import {
23
24
  ExtendedCameraControls,
24
25
  FEATURE_TYPE_RANKS,
25
26
  HANDLE_PIXELS,
26
- HEAD,
27
- HEAD_RADIUS,
28
27
  HIGHLIGHT_COLORS,
29
28
  HIGHLIGHT_WEIGHT,
30
29
  HOVER_WEIGHT,
@@ -52,7 +51,6 @@ import {
52
51
  REGION_ATTRIBUTE,
53
52
  SECTION_GIZMO_FRAME_SCALE,
54
53
  SECTION_RENDER_ORDER,
55
- SHAFT_RADIUS,
56
54
  SNAP_MARKER_PIXELS,
57
55
  SNAP_PIXELS,
58
56
  STOCK_OBJECT,
@@ -69,7 +67,7 @@ import {
69
67
  applyCapTheme,
70
68
  applyHighlightLayers,
71
69
  applyProjection,
72
- arrowPlacement,
70
+ arrowGeometry,
73
71
  aspectRatio,
74
72
  axesPlanes,
75
73
  bestOwner,
@@ -162,7 +160,7 @@ import {
162
160
  viewUp,
163
161
  viewVector,
164
162
  visualSurfaces
165
- } from "./chunk-WTTOUG4P.js";
163
+ } from "./chunk-7S6SIYHL.js";
166
164
 
167
165
  // src/render/feature-triangles.ts
168
166
  var EMPTY = { positions: new Float32Array(), normals: new Float32Array() };
@@ -268,6 +266,7 @@ var GridIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ j
268
266
  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" }) });
269
267
  var ToolsIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M9 2h6v7H9zM9.5 9v9.5l2.5 3.5 2.5-3.5V9M9.5 12l5 2.5M9.5 15.5l5 2.5" }) });
270
268
  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" }) });
269
+ var DfmIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M8.3 3h7.4L21 8.3v7.4L15.7 21H8.3L3 15.7V8.3zM12 7.5v5.5m0 3.5v.01" }) });
271
270
  var HoverIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v12H9l-5 4zm4 5h8m-8 3h5" }) });
272
271
  var FocusIcon = () => /* @__PURE__ */ jsx2(SvgIcon, { children: /* @__PURE__ */ jsx2("path", { d: "M4 4h16v16H4zM8 8h8v8H8zM2 12h4m12 0h4M12 2v4m0 12v4" }) });
273
272
  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" }) });
@@ -283,6 +282,7 @@ var ICONS = {
283
282
  banana: BananaIcon,
284
283
  tools: ToolsIcon,
285
284
  directions: DirectionsIcon,
285
+ dfm: DfmIcon,
286
286
  hover: HoverIcon,
287
287
  focus: FocusIcon,
288
288
  wireframe: WireframeIcon,
@@ -377,6 +377,14 @@ var DirectionsButton = () => /* @__PURE__ */ jsx3(
377
377
  label: () => "Highlight faces by direction"
378
378
  }
379
379
  );
380
+ var DfmButton = () => /* @__PURE__ */ jsx3(
381
+ ToolbarButton,
382
+ {
383
+ action: "dfm",
384
+ icon: "dfm",
385
+ label: (pressed) => pressed ? "Hide DFM flags" : "Show DFM flags"
386
+ }
387
+ );
380
388
  var HoverButton = () => /* @__PURE__ */ jsx3(
381
389
  ToolbarButton,
382
390
  {
@@ -435,6 +443,7 @@ var DefaultControls = () => {
435
443
  ].filter(Boolean);
436
444
  const analysis = [
437
445
  controls.directions ? /* @__PURE__ */ jsx3(DirectionsButton, {}, "directions") : null,
446
+ controls.dfm ? /* @__PURE__ */ jsx3(DfmButton, {}, "dfm") : null,
438
447
  controls.hover ? /* @__PURE__ */ jsx3(HoverButton, {}, "hover") : null,
439
448
  controls.focus ? /* @__PURE__ */ jsx3(FocusButton, {}, "focus") : null,
440
449
  controls.wireframe ? /* @__PURE__ */ jsx3(WireframeButton, {}, "wireframe") : null,
@@ -474,6 +483,7 @@ var ViewerToolbar = Object.assign(ViewerToolbarRoot, {
474
483
  BananaButton,
475
484
  ToolsButton,
476
485
  DirectionsButton,
486
+ DfmButton,
477
487
  HoverButton,
478
488
  FocusButton,
479
489
  WireframeButton,
@@ -1111,8 +1121,33 @@ var CubePanels = ({
1111
1121
 
1112
1122
  // src/direction-arrows.tsx
1113
1123
  import { useThree as useThree4 } from "@react-three/fiber";
1114
- import { useMemo as useMemo4 } from "react";
1115
- import { Quaternion as Quaternion2, Vector3 as Vector34 } from "three";
1124
+ import { useEffect as useEffect4, useMemo as useMemo4 } from "react";
1125
+ import { Quaternion as Quaternion2, Vector3 as Vector35 } from "three";
1126
+
1127
+ // src/render/directions.ts
1128
+ import { Vector3 as Vector34 } from "three";
1129
+ var LENGTH = 0.33;
1130
+ var GAP = 0.2;
1131
+ function arrowPlacement(direction, box) {
1132
+ const center = box.getCenter(new Vector34());
1133
+ const half = box.getSize(new Vector34()).multiplyScalar(0.5);
1134
+ const radius = half.length() || 1;
1135
+ const axis = new Vector34(direction.x, direction.y, direction.z);
1136
+ if (axis.lengthSq() === 0) axis.set(0, 0, 1);
1137
+ axis.normalize();
1138
+ let exit = Number.POSITIVE_INFINITY;
1139
+ for (const [component, extent] of [
1140
+ [axis.x, half.x],
1141
+ [axis.y, half.y],
1142
+ [axis.z, half.z]
1143
+ ]) {
1144
+ if (Math.abs(component) > 1e-6) exit = Math.min(exit, extent / Math.abs(component));
1145
+ }
1146
+ if (!Number.isFinite(exit)) exit = radius;
1147
+ return { tip: center.addScaledVector(axis, exit + radius * GAP), length: radius * LENGTH };
1148
+ }
1149
+
1150
+ // src/direction-arrows.tsx
1116
1151
  import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1117
1152
  var FURNITURE3 = { [EXCLUDE_FROM_FRAME]: true };
1118
1153
  var DirectionArrows = ({
@@ -1127,6 +1162,8 @@ var DirectionArrows = ({
1127
1162
  }) => {
1128
1163
  const box = useContentBox();
1129
1164
  const resolved = useMemo4(() => resolveTheme(theme), [theme]);
1165
+ const geometry = useMemo4(() => arrowGeometry(), []);
1166
+ useEffect4(() => () => geometry.dispose(), [geometry]);
1130
1167
  const shown = shownDirection === void 0 ? activeDirection : shownDirection;
1131
1168
  const drawn = (index) => {
1132
1169
  if (shown === null) return true;
@@ -1146,6 +1183,7 @@ var DirectionArrows = ({
1146
1183
  {
1147
1184
  direction,
1148
1185
  box,
1186
+ geometry,
1149
1187
  color: directionColor(index),
1150
1188
  opacity: 0.9,
1151
1189
  onPick: onPickDirection ? () => onPickDirection(index) : void 0
@@ -1158,28 +1196,37 @@ var DirectionArrows = ({
1158
1196
  {
1159
1197
  direction: named.direction,
1160
1198
  box,
1199
+ geometry,
1161
1200
  color: named.color,
1162
1201
  opacity: 0.9,
1163
1202
  onPick: onPickDirection ? () => onPickDirection(named.index) : void 0
1164
1203
  },
1165
1204
  `named-${named.index}`
1166
1205
  )),
1167
- previewDirection ? /* @__PURE__ */ jsx7(Arrow, { direction: previewDirection, box, color: resolved.hover, opacity: 0.95, onTop: true }) : null
1206
+ previewDirection ? /* @__PURE__ */ jsx7(
1207
+ Arrow,
1208
+ {
1209
+ direction: previewDirection,
1210
+ box,
1211
+ geometry,
1212
+ color: resolved.hover,
1213
+ opacity: 0.95,
1214
+ onTop: true
1215
+ }
1216
+ ) : null
1168
1217
  ] })
1169
1218
  );
1170
1219
  };
1171
- var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
1220
+ var Arrow = ({ direction, box, geometry, color, opacity, onPick, onTop = false }) => {
1172
1221
  const invalidate = useThree4((state) => state.invalidate);
1173
1222
  const { tip, length, quaternion } = useMemo4(() => {
1174
1223
  const placement = arrowPlacement(direction, box);
1175
1224
  const aim = new Quaternion2().setFromUnitVectors(
1176
- CONE_AXIS,
1177
- new Vector34(direction.x, direction.y, direction.z).normalize()
1225
+ ARROW_AXIS,
1226
+ new Vector35(direction.x, direction.y, direction.z).normalize()
1178
1227
  );
1179
1228
  return { tip: placement.tip, length: placement.length, quaternion: aim };
1180
1229
  }, [box, direction]);
1181
- const head = length * HEAD;
1182
- const shaft = length * (1 - HEAD);
1183
1230
  const press = onPick ? {
1184
1231
  onClick: (event) => {
1185
1232
  event.stopPropagation();
@@ -1187,32 +1234,23 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
1187
1234
  invalidate();
1188
1235
  }
1189
1236
  } : {};
1190
- return /* @__PURE__ */ jsxs3("group", { position: tip, quaternion, renderOrder: onTop ? 10 : 3, children: [
1191
- /* @__PURE__ */ jsxs3("mesh", { position: [0, head * 0.5, 0], rotation: [Math.PI, 0, 0], ...press, children: [
1192
- /* @__PURE__ */ jsx7("coneGeometry", { args: [length * HEAD_RADIUS, head, 20] }),
1193
- /* @__PURE__ */ jsx7("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
1194
- ] }),
1195
- /* @__PURE__ */ jsxs3("mesh", { position: [0, head + shaft * 0.5, 0], ...press, children: [
1196
- /* @__PURE__ */ jsx7("cylinderGeometry", { args: [length * SHAFT_RADIUS, length * SHAFT_RADIUS, shaft, 12] }),
1197
- /* @__PURE__ */ jsx7("meshBasicMaterial", { color, transparent: true, opacity, depthTest: !onTop })
1198
- ] })
1199
- ] });
1237
+ return /* @__PURE__ */ jsx7("group", { position: tip, quaternion, scale: length, renderOrder: onTop ? 10 : 3, children: /* @__PURE__ */ jsx7(ArrowMesh, { geometry, color, opacity, onTop, events: press }) });
1200
1238
  };
1201
1239
 
1202
1240
  // src/section-tool.tsx
1203
1241
  import { useFrame, useThree as useThree5 } from "@react-three/fiber";
1204
- import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
1242
+ import { useEffect as useEffect5, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
1205
1243
  import {
1206
1244
  BufferGeometry as BufferGeometry2,
1207
1245
  DoubleSide,
1208
1246
  Quaternion as Quaternion3,
1209
1247
  Raycaster,
1210
1248
  Vector2,
1211
- Vector3 as Vector35
1249
+ Vector3 as Vector36
1212
1250
  } from "three";
1213
1251
  import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1214
1252
  var FURNITURE4 = { [EXCLUDE_FROM_FRAME]: true };
1215
- var Z = new Vector35(0, 0, 1);
1253
+ var Z = new Vector36(0, 0, 1);
1216
1254
  var AXIS_ROTATIONS = {
1217
1255
  x: [0, Math.PI / 2, 0],
1218
1256
  y: [Math.PI / 2, 0, 0],
@@ -1225,11 +1263,11 @@ var SectionTool = ({ theme }) => {
1225
1263
  const section = useSyncExternalStore(store.subscribe, store.get, store.get);
1226
1264
  const box = useContentBox();
1227
1265
  const resolved = useMemo5(() => resolveTheme(theme), [theme]);
1228
- useEffect4(() => {
1266
+ useEffect5(() => {
1229
1267
  store.setEngaged(true);
1230
1268
  return () => store.setEngaged(false);
1231
1269
  }, [store]);
1232
- useEffect4(() => {
1270
+ useEffect5(() => {
1233
1271
  if (!section) return void 0;
1234
1272
  const clear = (event) => {
1235
1273
  if (event.key === "Escape") store.set(null);
@@ -1243,14 +1281,14 @@ var SectionTool = ({ theme }) => {
1243
1281
  var useSquare = () => {
1244
1282
  const square = useMemo5(
1245
1283
  () => new BufferGeometry2().setFromPoints([
1246
- new Vector35(-1, 1, 0),
1247
- new Vector35(1, 1, 0),
1248
- new Vector35(1, -1, 0),
1249
- new Vector35(-1, -1, 0)
1284
+ new Vector36(-1, 1, 0),
1285
+ new Vector36(1, 1, 0),
1286
+ new Vector36(1, -1, 0),
1287
+ new Vector36(-1, -1, 0)
1250
1288
  ]),
1251
1289
  []
1252
1290
  );
1253
- useEffect4(() => () => square.dispose(), [square]);
1291
+ useEffect5(() => () => square.dispose(), [square]);
1254
1292
  return square;
1255
1293
  };
1256
1294
  var Picker = ({ box, theme }) => {
@@ -1264,17 +1302,17 @@ var Picker = ({ box, theme }) => {
1264
1302
  const previewRef = useRef3(null);
1265
1303
  const hover = useRef3(null);
1266
1304
  const [hoveredPlane, setHoveredPlane] = useState4(null);
1267
- const span = useMemo5(() => box.getSize(new Vector35()).length(), [box]);
1268
- const centre = useMemo5(() => box.getCenter(new Vector35()), [box]);
1305
+ const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1306
+ const centre = useMemo5(() => box.getCenter(new Vector36()), [box]);
1269
1307
  const previewSize = span * PREVIEW_SCALE;
1270
- useEffect4(() => {
1308
+ useEffect5(() => {
1271
1309
  store.setPicking(true);
1272
1310
  return () => store.setPicking(false);
1273
1311
  }, [store]);
1274
- useEffect4(() => {
1312
+ useEffect5(() => {
1275
1313
  const raycaster = new Raycaster();
1276
1314
  const pointer = new Vector2();
1277
- const aim = new Vector35();
1315
+ const aim = new Vector36();
1278
1316
  const hide = () => {
1279
1317
  if (!hover.current) return;
1280
1318
  hover.current = null;
@@ -1418,7 +1456,7 @@ var Picker = ({ box, theme }) => {
1418
1456
  var CutSheet = ({ box, section, theme }) => {
1419
1457
  const square = useSquare();
1420
1458
  const cut = useMemo5(() => resolveSectionPlane(section, box), [box, section]);
1421
- const span = useMemo5(() => box.getSize(new Vector35()).length(), [box]);
1459
+ const span = useMemo5(() => box.getSize(new Vector36()).length(), [box]);
1422
1460
  const placement = useMemo5(() => {
1423
1461
  if (!cut) return null;
1424
1462
  const position = sectionAnchor(box, cut.plane).addScaledVector(
@@ -1469,7 +1507,7 @@ var CutSheet = ({ box, section, theme }) => {
1469
1507
  import { Html, Line } from "@react-three/drei";
1470
1508
  import { useFrame as useFrame2, useThree as useThree6 } from "@react-three/fiber";
1471
1509
  import {
1472
- useEffect as useEffect5,
1510
+ useEffect as useEffect6,
1473
1511
  useLayoutEffect as useLayoutEffect3,
1474
1512
  useMemo as useMemo6,
1475
1513
  useRef as useRef4,
@@ -1481,18 +1519,18 @@ import {
1481
1519
  Raycaster as Raycaster2,
1482
1520
  SphereGeometry,
1483
1521
  Vector2 as Vector22,
1484
- Vector3 as Vector37
1522
+ Vector3 as Vector38
1485
1523
  } from "three";
1486
1524
 
1487
1525
  // src/render/resample.ts
1488
- import { Vector3 as Vector36 } from "three";
1526
+ import { Vector3 as Vector37 } from "three";
1489
1527
  var ON_PLANE = 1e-7;
1490
1528
  var STITCH = 1e-5;
1491
1529
  var COLLINEAR = 1e-6;
1492
1530
  function clipSegments(positions, plane) {
1493
1531
  const out = [];
1494
- const a = new Vector36();
1495
- const b = new Vector36();
1532
+ const a = new Vector37();
1533
+ const b = new Vector37();
1496
1534
  const count = Math.floor(positions.length / 6);
1497
1535
  for (let i = 0; i < count; i += 1) {
1498
1536
  a.set(positions[i * 6], positions[i * 6 + 1], positions[i * 6 + 2]);
@@ -1507,11 +1545,11 @@ function clipSegments(positions, plane) {
1507
1545
  return Float32Array.from(out);
1508
1546
  }
1509
1547
  function sectionContour(positions, regionOf, plane) {
1510
- const a = new Vector36();
1511
- const b = new Vector36();
1512
- const c = new Vector36();
1513
- const p = new Vector36();
1514
- const q = new Vector36();
1548
+ const a = new Vector37();
1549
+ const b = new Vector37();
1550
+ const c = new Vector37();
1551
+ const p = new Vector37();
1552
+ const q = new Vector37();
1515
1553
  const byRegion = /* @__PURE__ */ new Map();
1516
1554
  const triangleCount = Math.floor(positions.length / 9);
1517
1555
  for (let t = 0; t < triangleCount; t += 1) {
@@ -1569,17 +1607,17 @@ function stitch(segments, out) {
1569
1607
  return into.set(segments[o], segments[o + 1], segments[o + 2]);
1570
1608
  };
1571
1609
  const direction = (i, into) => {
1572
- const s = new Vector36();
1573
- const e = new Vector36();
1610
+ const s = new Vector37();
1611
+ const e = new Vector37();
1574
1612
  point(i, 0, s);
1575
1613
  point(i, 1, e);
1576
1614
  return into.subVectors(e, s).normalize();
1577
1615
  };
1578
1616
  const consumed = new Uint8Array(count);
1579
- const d = new Vector36();
1580
- const n = new Vector36();
1617
+ const d = new Vector37();
1618
+ const n = new Vector37();
1581
1619
  const next = (i, end) => {
1582
- const at = point(i, end, new Vector36());
1620
+ const at = point(i, end, new Vector37());
1583
1621
  const list = ends.get(key(at.x, at.y, at.z)) ?? [];
1584
1622
  if (list.length !== 2) return null;
1585
1623
  const other = list.find((entry) => entry >> 1 !== i);
@@ -1608,8 +1646,8 @@ function stitch(segments, out) {
1608
1646
  };
1609
1647
  const back = walk(0);
1610
1648
  const forward = walk(1);
1611
- const s = point(back.index, back.end, new Vector36());
1612
- const e = point(forward.index, forward.end, new Vector36());
1649
+ const s = point(back.index, back.end, new Vector37());
1650
+ const e = point(forward.index, forward.end, new Vector37());
1613
1651
  out.push(s.x, s.y, s.z, e.x, e.y, e.z);
1614
1652
  }
1615
1653
  }
@@ -1617,8 +1655,8 @@ function pointInContour(point, plane, contour) {
1617
1655
  const [u, v] = planeAxes(plane);
1618
1656
  const px = point.dot(u);
1619
1657
  const py = point.dot(v);
1620
- const a = new Vector36();
1621
- const b = new Vector36();
1658
+ const a = new Vector37();
1659
+ const b = new Vector37();
1622
1660
  let inside = false;
1623
1661
  const count = Math.floor(contour.length / 6);
1624
1662
  for (let i = 0; i < count; i += 1) {
@@ -1635,15 +1673,15 @@ function pointInContour(point, plane, contour) {
1635
1673
  return inside;
1636
1674
  }
1637
1675
  function capHit(ray, plane, contour) {
1638
- const point = ray.intersectPlane(plane, new Vector36());
1676
+ const point = ray.intersectPlane(plane, new Vector37());
1639
1677
  if (!point) return null;
1640
1678
  return pointInContour(point, plane, contour) ? point : null;
1641
1679
  }
1642
1680
  function planeAxes(plane) {
1643
1681
  const n = plane.normal;
1644
- const seed = Math.abs(n.x) < 0.9 ? new Vector36(1, 0, 0) : new Vector36(0, 1, 0);
1645
- const u = new Vector36().crossVectors(n, seed).normalize();
1646
- const v = new Vector36().crossVectors(n, u).normalize();
1682
+ const seed = Math.abs(n.x) < 0.9 ? new Vector37(1, 0, 0) : new Vector37(0, 1, 0);
1683
+ const u = new Vector37().crossVectors(n, seed).normalize();
1684
+ const v = new Vector37().crossVectors(n, u).normalize();
1647
1685
  return [u, v];
1648
1686
  }
1649
1687
  function sampleSection(meshPositions, regionOf, edgePositions, plane) {
@@ -1676,11 +1714,11 @@ var MeasureTool = ({
1676
1714
  const controlled = measurements !== void 0;
1677
1715
  const list = controlled ? measurements : held;
1678
1716
  const [draft, setDraft] = useState5([]);
1679
- useEffect5(() => {
1717
+ useEffect6(() => {
1680
1718
  store.setEngaged(true);
1681
1719
  return () => store.setEngaged(false);
1682
1720
  }, [store]);
1683
- useEffect5(() => {
1721
+ useEffect6(() => {
1684
1722
  setDraft([]);
1685
1723
  }, [mode]);
1686
1724
  const commit = (next) => {
@@ -1689,7 +1727,7 @@ var MeasureTool = ({
1689
1727
  };
1690
1728
  const latest = useRef4({ list, commit });
1691
1729
  latest.current = { list, commit };
1692
- useEffect5(() => {
1730
+ useEffect6(() => {
1693
1731
  const onKey = (event) => {
1694
1732
  if (event.key === "Escape") {
1695
1733
  setDraft([]);
@@ -1753,7 +1791,7 @@ var MeasureTool = ({
1753
1791
  ] });
1754
1792
  };
1755
1793
  var NO_POINTER = { pointerEvents: "none" };
1756
- var NOWHERE = [new Vector37(), new Vector37()];
1794
+ var NOWHERE = [new Vector38(), new Vector38()];
1757
1795
  var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1758
1796
  const camera = useThree6((state) => state.camera);
1759
1797
  const scene = useThree6((state) => state.scene);
@@ -1774,7 +1812,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1774
1812
  }, []);
1775
1813
  const current = useRef4({ mode, draft, onPlace, format, size });
1776
1814
  current.current = { mode, draft, onPlace, format, size };
1777
- useEffect5(() => {
1815
+ useEffect6(() => {
1778
1816
  const raycaster = new Raycaster2();
1779
1817
  const pointer = new Vector22();
1780
1818
  const samples = new SampleCache();
@@ -1806,7 +1844,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1806
1844
  const snapped = snapAt(target, target.edges, camera, viewport);
1807
1845
  const from = placed[placed.length - 1];
1808
1846
  const lock = shift && from ? lockToAxis(from, snapped.point) : null;
1809
- const found = lock ? { ...snapped, point: new Vector37(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1847
+ const found = lock ? { ...snapped, point: new Vector38(lock.point.x, lock.point.y, lock.point.z), edge: null } : snapped;
1810
1848
  snap.current = found;
1811
1849
  const node = marker.current;
1812
1850
  if (node) {
@@ -1880,7 +1918,7 @@ var Snapper = ({ mode, draft, onPlace, theme, format, label }) => {
1880
1918
  window.removeEventListener("keyup", key);
1881
1919
  };
1882
1920
  }, [camera, domElement, invalidate, isTap, scene, theme.measure]);
1883
- useEffect5(() => {
1921
+ useEffect6(() => {
1884
1922
  if (draft.length === 0 && band.current) {
1885
1923
  band.current.visible = false;
1886
1924
  if (bandLabel.current) bandLabel.current.visible = false;
@@ -2114,7 +2152,7 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2114
2152
  const lines = useRef4([]);
2115
2153
  const centre = useMemo6(() => {
2116
2154
  const at = midpoint(a, b);
2117
- return new Vector37(at.x, at.y, at.z);
2155
+ return new Vector38(at.x, at.y, at.z);
2118
2156
  }, [a, b]);
2119
2157
  useFrame2(() => {
2120
2158
  const dash = screenLength(camera, centre, size, DELTA_DASH_PIXELS);
@@ -2136,8 +2174,8 @@ var DeltaLegs = ({ a, b, id, format, label }) => {
2136
2174
  lines.current[index] = line;
2137
2175
  },
2138
2176
  points: [
2139
- new Vector37(leg.from.x, leg.from.y, leg.from.z),
2140
- new Vector37(leg.to.x, leg.to.y, leg.to.z)
2177
+ new Vector38(leg.from.x, leg.from.y, leg.from.z),
2178
+ new Vector38(leg.to.x, leg.to.y, leg.to.z)
2141
2179
  ],
2142
2180
  color: AXIS_COLORS[leg.axis],
2143
2181
  lineWidth: MEASURE_LINE_PIXELS.leg,
@@ -2173,7 +2211,7 @@ var ScreenDot = ({ point, pixels, color }) => {
2173
2211
  const invalidate = useThree6((state) => state.invalidate);
2174
2212
  const node = useRef4(null);
2175
2213
  const dot = useDot();
2176
- const position = useMemo6(() => new Vector37(point.x, point.y, point.z), [point]);
2214
+ const position = useMemo6(() => new Vector38(point.x, point.y, point.z), [point]);
2177
2215
  useFrame2(() => {
2178
2216
  const group = node.current;
2179
2217
  if (!group) return;
@@ -2188,7 +2226,7 @@ var ScreenDot = ({ point, pixels, color }) => {
2188
2226
  var Segment = ({ points, color }) => {
2189
2227
  const [a, b] = points;
2190
2228
  const ends = useMemo6(
2191
- () => [new Vector37(a.x, a.y, a.z), new Vector37(b.x, b.y, b.z)],
2229
+ () => [new Vector38(a.x, a.y, a.z), new Vector38(b.x, b.y, b.z)],
2192
2230
  // Keyed on the numbers: a draft's tuple is a fresh array on every render,
2193
2231
  // and a rebuilt array would rebuild the line's geometry with it.
2194
2232
  [a.x, a.y, a.z, b.x, b.y, b.z]
@@ -2213,7 +2251,7 @@ var Polyline = ({ points, color }) => {
2213
2251
  };
2214
2252
  function useDot() {
2215
2253
  const geometry = useMemo6(() => new SphereGeometry(1, 16, 12), []);
2216
- useEffect5(() => () => geometry.dispose(), [geometry]);
2254
+ useEffect6(() => () => geometry.dispose(), [geometry]);
2217
2255
  return geometry;
2218
2256
  }
2219
2257
  function isEditing(target) {
@@ -2230,7 +2268,7 @@ import { useMemo as useMemo7 } from "react";
2230
2268
  import { DoubleSide as DoubleSide2 } from "three";
2231
2269
 
2232
2270
  // src/render/tool-marks.ts
2233
- import { Quaternion as Quaternion4, Vector2 as Vector23, Vector3 as Vector38 } from "three";
2271
+ import { Quaternion as Quaternion4, Vector2 as Vector23, Vector3 as Vector39 } from "three";
2234
2272
  var TOOL_CORNER_STEPS = 12;
2235
2273
  var TOOL_ROUND = 64;
2236
2274
  function toolProfile(radius, corner, height) {
@@ -2250,9 +2288,9 @@ function toolProfile(radius, corner, height) {
2250
2288
  points.push(new Vector23(radius, Math.max(height, rounded)));
2251
2289
  return points;
2252
2290
  }
2253
- var UP = new Vector38(0, 1, 0);
2254
- var AXES = [new Vector38(1, 0, 0), new Vector38(0, 1, 0), new Vector38(0, 0, 1)];
2255
- var toVector = ({ x, y, z }) => new Vector38(x, y, z);
2291
+ var UP = new Vector39(0, 1, 0);
2292
+ var AXES = [new Vector39(1, 0, 0), new Vector39(0, 1, 0), new Vector39(0, 0, 1)];
2293
+ var toVector = ({ x, y, z }) => new Vector39(x, y, z);
2256
2294
  var squareTo = (w, along) => {
2257
2295
  const wanted = along ? toVector(along) : null;
2258
2296
  const flat = wanted?.clone().addScaledVector(w, -wanted.dot(w));
@@ -2272,7 +2310,7 @@ function toolMarkShape(mark) {
2272
2310
  if (!(mark.diameter > 0) || axis.lengthSq() === 0) return null;
2273
2311
  const w = axis.normalize();
2274
2312
  const u = squareTo(w, mark.across);
2275
- const v = new Vector38().crossVectors(w, u);
2313
+ const v = new Vector39().crossVectors(w, u);
2276
2314
  const radius = mark.diameter / 2;
2277
2315
  const corner = Math.min(Math.max(mark.cornerRadius ?? 0, 0), radius);
2278
2316
  const height = Math.max(mark.height, corner);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolpath/viewer",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "React Three Fiber viewer for Toolpath Engine part reports",
5
5
  "license": "MIT",
6
6
  "engines": {