@toolpath/viewer 0.4.0 → 1.0.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.
@@ -1,5 +1,5 @@
1
- import { P as PartModel, a as PartMeshRefs } from '../normalize-BXIGoZmc.js';
2
- export { E as EnginePart, b as EnginePartProps, M as MIN_KERNEL_VERSION, c as assertSupportedKernelVersion, n as normalizePartReport, s as smoothRegionNormals } from '../normalize-BXIGoZmc.js';
1
+ import { P as PartModel, a as PartMeshRefs } from '../normalize-DsoIyM8B.js';
2
+ export { E as EnginePart, b as EnginePartProps, M as MIN_KERNEL_VERSION, c as assertSupportedKernelVersion, n as normalizePartReport, s as smoothRegionNormals } from '../normalize-DsoIyM8B.js';
3
3
  import { BufferGeometry } from 'three';
4
4
  import 'react';
5
5
 
@@ -12,7 +12,7 @@ import {
12
12
  parsePartGeometry,
13
13
  partMeshAssets,
14
14
  smoothRegionNormals
15
- } from "../chunk-ILZ2KQUK.js";
15
+ } from "../chunk-4BU4QZCL.js";
16
16
  export {
17
17
  EnginePart,
18
18
  MIN_KERNEL_VERSION,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { P as PartModel, V as Vec3, d as ViewerTheme, e as PartModelRegion, f as ViewerCamera, g as ViewerView, h as Projection, T as TriangleRange, F as FeatureTag, R as RegionIndex, i as PartModelFeature, j as FeatureType } from './normalize-BXIGoZmc.js';
2
- export { B as BuildPickInput, C as CAD_CAMERA_UP, k as CANDIDATE_WEIGHT, D as DEFAULT_FIT_MARGIN, l as DEFAULT_THEME, m as DIRECTION_COLORS, o as EXCLUDE_FROM_FRAME, E as EnginePart, p as FeatureHighlight, H as HANDLE_PIXELS, q as HIGHLIGHT_COLORS, r as HIGHLIGHT_WEIGHT, t as HOVER_WEIGHT, u as HighlightLayers, K as KnownFeatureType, N as NO_MODIFIERS, v as PERSPECTIVE_FOV, w as PICKED_SURFACE_LABEL, x as PartMesh, y as PartMeshProps, a as PartMeshRefs, z as PartObject, A as PartPick, G as PickModifiers, I as REGION_ATTRIBUTE, J as RegionHighlight, L as RegionPaint, S as SECTION_RENDER_ORDER, O as SceneBounds, Q as SectionAnchor, U as SectionBounds, W as SectionOptions, X as SectionPlacement, Y as SectionState, Z as SectionView, _ as ViewportSize, $ as applyHighlightLayers, a0 as applyProjection, a1 as aspectRatio, a2 as boundsFromBox, a3 as buildPick, a4 as buildRegionAttribute, a5 as buildRegionTexels, a6 as cadViewDirections, a7 as contentBounds, a8 as createPart, a9 as currentViewDirection, aa as defaultBounds, ab as directionColor, ac as dragPlane, ad as fitDistance, ae as focusForPick, n as normalizePartReport, af as orthographicHalfHeight, ag as perspectiveFitDistance, ah as pickedStartDepth, ai as resolveSectionPlane, aj as resolveTheme, ak as screenLength, al as sectionBounds, am as sectionConstant, an as sectionDepth, ao as sectionDepthConstant, ap as sectionDepthRange, aq as sectionFromPick, ar as sectionOffset, as as sectionPlane, s as smoothRegionNormals, at as startPosition, au as themesEqual, av as viewDirection } from './normalize-BXIGoZmc.js';
3
- import { BufferGeometry, CanvasTexture, Vector3, Box3 } from 'three';
1
+ import { P as PartModel, V as Vec3, d as ViewerTheme, e as PartModelRegion, T as TriangleRange, F as FeatureTag, R as RegionIndex, f as PartModelFeature, g as FeatureType } from './normalize-DsoIyM8B.js';
2
+ export { B as BuildPickInput, C as CANDIDATE_WEIGHT, D as DEFAULT_THEME, h as DIRECTION_COLORS, E as EnginePart, i as FeatureHighlight, H as HANDLE_PIXELS, j as HIGHLIGHT_COLORS, k as HIGHLIGHT_WEIGHT, l as HOVER_WEIGHT, m as HighlightLayers, K as KnownFeatureType, N as NO_MODIFIERS, o as PICKED_SURFACE_LABEL, p as PartMesh, q as PartMeshProps, a as PartMeshRefs, r as PartObject, t as PartPick, u as PickModifiers, v as REGION_ATTRIBUTE, w as RegionHighlight, x as RegionPaint, S as SECTION_RENDER_ORDER, y as SectionAnchor, z as SectionBounds, A as SectionOptions, G as SectionPlacement, I as SectionState, J as SectionView, L as applyHighlightLayers, O as buildPick, Q as buildRegionAttribute, U as buildRegionTexels, W as createPart, X as directionColor, Y as dragPlane, Z as focusForPick, n as normalizePartReport, _ as pickedStartDepth, $ as resolveSectionPlane, a0 as resolveTheme, a1 as sectionBounds, a2 as sectionConstant, a3 as sectionDepth, a4 as sectionDepthConstant, a5 as sectionDepthRange, a6 as sectionFromPick, a7 as sectionOffset, a8 as sectionPlane, s as smoothRegionNormals, a9 as themesEqual, aa as viewDirection } from './normalize-DsoIyM8B.js';
3
+ import { BufferGeometry, CanvasTexture, Vector3, Box3, OrthographicCamera, PerspectiveCamera, Object3D } from 'three';
4
4
  import * as react from 'react';
5
5
  import { RefObject, ReactNode, CSSProperties } from 'react';
6
6
  import CameraControls from 'camera-controls';
@@ -269,6 +269,15 @@ interface DoubleTapPoint extends TapPoint {
269
269
  interface DoubleTapTracker {
270
270
  /** Whether this press completes a double tap begun by the last one. */
271
271
  isDouble(event: DoubleTapPoint): boolean;
272
+ /**
273
+ * Forgets the press being waited on, so the next one starts a fresh pair.
274
+ *
275
+ * For the caller that knows the gesture was interrupted by something this
276
+ * has no way to see — the pointer leaving the thing being clicked, most of
277
+ * all. Two presses either side of a trip to a panel are two clicks that
278
+ * happen to be quick, however close together the clock puts them.
279
+ */
280
+ reset(): void;
272
281
  }
273
282
  /**
274
283
  * Pairing presses into a double tap by hand.
@@ -284,16 +293,29 @@ interface DoubleTapTracker {
284
293
  *
285
294
  * A completed double clears the state rather than leaving it, so three presses
286
295
  * are one double and one single rather than two overlapping doubles.
296
+ *
297
+ * Time and position are all this can judge on, which is not enough on its own:
298
+ * clicking a face, pressing something in a panel and clicking the same face
299
+ * again is three gestures the clock cannot tell from two, and it is quick
300
+ * enough to land inside the window. {@link DoubleTapTracker.reset} is how the
301
+ * caller says the pair was broken by something it could see and this could not.
287
302
  */
288
303
  declare function trackDoubleTaps(within?: number, slop?: number): DoubleTapTracker;
289
304
 
305
+ /** Whether the gesture ending at this event was a click rather than a drag. */
306
+ type TapGuard = (event: TapPoint) => boolean;
290
307
  /**
291
308
  * A click that is a click, for anything inside the canvas.
292
309
  *
293
310
  * Bound to the canvas element rather than to the object, because the gesture
294
311
  * being judged started before any object knew about it.
312
+ *
313
+ * Under a `<Viewer>` this returns the tracker the scene already owns and
314
+ * attaches nothing. Called outside one — it is exported, so a consumer may use
315
+ * it in a scene of their own — it owns a tracker of its own instead. The effect
316
+ * is what differs, not the hooks: they run in the same order either way.
295
317
  */
296
- declare function useTapGuard(): (event: TapPoint) => boolean;
318
+ declare function useTapGuard(): TapGuard;
297
319
 
298
320
  interface GridSpec {
299
321
  /** Cell size in part units (millimetres). */
@@ -364,6 +386,200 @@ type SurfaceOf = ReadonlyMap<number, number>;
364
386
  */
365
387
  declare function visualSurfaces(regions: readonly PartModelRegion[]): SurfaceOf;
366
388
 
389
+ type Projection = 'orthographic' | 'perspective';
390
+ type ViewerCamera = OrthographicCamera | PerspectiveCamera;
391
+ interface ViewportSize {
392
+ readonly width: number;
393
+ readonly height: number;
394
+ }
395
+ /** Vertical field of view, in degrees, for the perspective camera. */
396
+ declare const PERSPECTIVE_FOV = 30;
397
+ /** Padding around the framed bounds, as a multiple of its radius. */
398
+ declare const DEFAULT_FIT_MARGIN = 1.2;
399
+ /** Marks scene furniture — grid, axes — that the camera should not frame. */
400
+ declare const EXCLUDE_FROM_FRAME = "viewerExcludeFromFrame";
401
+ /**
402
+ * What the camera frames: a bounding *sphere*, not a box.
403
+ *
404
+ * A sphere makes framing rotation-invariant — the part stays fully visible from
405
+ * every angle, and a resize never needs to know the current pose. Framing
406
+ * per-axis box dimensions instead means recomputing the camera distance on
407
+ * every resize, and still clipping on some orientations.
408
+ */
409
+ interface SceneBounds {
410
+ readonly center: Vector3;
411
+ readonly radius: number;
412
+ }
413
+ /**
414
+ * Bounds for a scene with nothing in it. A viewer is mounted before it has a
415
+ * part, and a camera with a zero-size frustum renders nothing at all.
416
+ */
417
+ declare function defaultBounds(): SceneBounds;
418
+ /**
419
+ * A container can be laid out at zero size, and a degenerate aspect ratio
420
+ * produces a `NaN` projection matrix that never recovers. Fall back to square.
421
+ */
422
+ declare function aspectRatio(size: ViewportSize): number;
423
+ /**
424
+ * A world length that covers `pixels` on screen at `point`.
425
+ *
426
+ * Anything sized in world units is a thumbnail on a plate and a wall on an
427
+ * insert; the whole reason a control is measured this way is that it is a
428
+ * control rather than part of the model. The section handle and the orbit
429
+ * target marker both size themselves with it, which is why it lives here with
430
+ * the camera rather than beside either of them.
431
+ *
432
+ * The two projections answer differently on purpose. An orthographic frustum
433
+ * covers the same world height wherever the point is, so distance does not
434
+ * enter; a perspective one covers more the further away it looks, so it does.
435
+ */
436
+ declare function screenLength(camera: ViewerCamera, point: Vector3, viewport: ViewportSize, pixels: number): number;
437
+ declare function boundsFromBox(box: Box3): SceneBounds;
438
+ /**
439
+ * The bounds of everything worth framing under `root`.
440
+ *
441
+ * Skips objects flagged with {@link EXCLUDE_FROM_FRAME} and their descendants:
442
+ * a 100 mm grid around a 6 mm part would otherwise frame the grid, and the part
443
+ * would be a speck.
444
+ */
445
+ declare function contentBounds(root: Object3D, into: Box3): SceneBounds;
446
+ /**
447
+ * The distance at which a sphere of `radius` fits inside a perspective frustum.
448
+ * Uses the narrower of the two field-of-view angles, so a portrait viewport
449
+ * frames on width and a landscape one on height.
450
+ */
451
+ declare function perspectiveFitDistance(fovDegrees: number, aspect: number, radius: number): number;
452
+ /**
453
+ * Half-height of an orthographic frustum that fits a sphere of `radius`. A
454
+ * portrait viewport grows the height so the width still clears the sphere.
455
+ */
456
+ declare function orthographicHalfHeight(aspect: number, radius: number): number;
457
+ declare function fitDistance(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): number;
458
+ declare function startPosition(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): Vector3;
459
+ /**
460
+ * How far out the wheel may pull, as a fraction of the fitted framing: the part
461
+ * may shrink to a quarter of the view and no further. Past that it is a speck
462
+ * with no cue that Fit is the way back.
463
+ */
464
+ declare const MIN_FRAME_RATIO = 0.25;
465
+ /**
466
+ * How far in the wheel may push, as a multiple of the fitted framing.
467
+ *
468
+ * Both ratios are the legacy viewer's `minZoom` 0.25 / `maxZoom` 10
469
+ * (`three-object.tsx:289-311`), which transfer almost exactly: legacy's
470
+ * orthographic frustum was `largestDimension * 2` tall and this one is the
471
+ * fitted bounding sphere, which for a cube agree within a few percent. So the
472
+ * numbers are the ones that shipped, and here they carry a meaning they did not
473
+ * have there — `zoom` 1 *is* the fitted framing, so 0.25 and 10 say exactly
474
+ * what a reader thinks they say.
475
+ */
476
+ declare const MAX_FRAME_RATIO = 10;
477
+ /** How far the camera may travel, in the two units the two cameras travel in. */
478
+ interface CameraLimits {
479
+ readonly minZoom: number;
480
+ readonly maxZoom: number;
481
+ readonly minDistance: number;
482
+ readonly maxDistance: number;
483
+ }
484
+ /**
485
+ * The clamps for a scene, so the wheel cannot leave the viewport empty.
486
+ *
487
+ * Without them it can, from either end, and both cameras can do it: an
488
+ * orthographic `camera.zoom` reaches 1e30 in sixty notches, and a perspective
489
+ * camera dives inside the part in eight, because `minDistance` defaults to
490
+ * `Number.EPSILON`. Fit recovers from both — but Fit is a double middle click
491
+ * and nothing on screen says so, which makes it a way out rather than an
492
+ * answer.
493
+ *
494
+ * One rule for both cameras: the wheel may take the part from a quarter of its
495
+ * fitted size to ten times it. Under an orthographic camera that scale *is* the
496
+ * frustum, so the ratios land on `zoom`; under a perspective camera apparent
497
+ * size is the inverse of distance, so they land on `distance` — which is why
498
+ * they swap sides between the two.
499
+ *
500
+ * Pure, and derived from `bounds`, so it re-derives wherever the scene is
501
+ * re-measured rather than being sampled once at mount.
502
+ *
503
+ * `framed` is what the view has been framed *on*, when that is not the whole
504
+ * scene — `frameBox` on a single feature. The band is then taken about that
505
+ * framing as well as about the scene's, because a band centred on the part is
506
+ * the wrong band for a view of a 3 mm hole in it: ten times the *part's* fitted
507
+ * size can be less than once the hole's, so the framing the caller asked for sat
508
+ * outside the clamps meant to keep it in view. Under an orthographic camera
509
+ * `zoom` is measured against a frustum still sized to the scene, so the framing
510
+ * enters as the zoom ratio it takes to reach; under a perspective one it enters
511
+ * as its own fitted distance.
512
+ *
513
+ * Widened to the union of the two rather than moved onto `framed`, and that is
514
+ * the whole of why this takes both. A band that simply followed the last framing
515
+ * would take the part away: framing a hole would put "far enough out to see what
516
+ * the hole is in" past `maxDistance`, so the wheel could no longer get back to
517
+ * the part it had just been looking at, and Fit — a gesture with nothing on
518
+ * screen to say so — would be the only way. Reaching further in must not cost
519
+ * the reach back out, so each end takes whichever bound is looser. With no
520
+ * `framed`, or one the size of the scene, both ends collapse to the scene's own
521
+ * and this is the function it was before.
522
+ */
523
+ declare function cameraLimits(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number, framed?: SceneBounds): CameraLimits;
524
+ /**
525
+ * The box the orbit target is confined to — the fitted framing, several frames
526
+ * over. Written into `into` so the per-frame path allocates nothing.
527
+ */
528
+ declare function targetBoundary(bounds: SceneBounds, into: Box3, margin?: number): Box3;
529
+ /**
530
+ * Points an existing camera's frustum at `bounds` for the current viewport.
531
+ * Pose is untouched — that belongs to the controls.
532
+ */
533
+ declare function applyProjection(camera: ViewerCamera, size: ViewportSize, bounds: SceneBounds, margin?: number): void;
534
+ /**
535
+ * The world-up convention. The part data is Z-up (millimetres, no conversion) —
536
+ * not the glTF-conventional Y-up — so the camera has to say so explicitly.
537
+ */
538
+ declare const CAD_CAMERA_UP: Vector3;
539
+ /**
540
+ * `up` re-squared against a view direction: the part of it perpendicular to
541
+ * `view`, written into `into`.
542
+ *
543
+ * This is what keeps the horizon level while a free orbit carries the camera
544
+ * over a pole — the up vector follows the view instead of flipping. The
545
+ * controls run it on every update, which is what makes the next sentence the
546
+ * important one.
547
+ *
548
+ * **It is path-dependent.** It is a projection, so it discards the component it
549
+ * removes and cannot put it back: running it at one view and then another does
550
+ * not give what running it once at the second view would. So a camera carries
551
+ * the roll of every pose it has passed through, and any pose that is meant to
552
+ * be canonical — the opening frame, a reset, a named view — has to say what
553
+ * `up` is rather than inherit it. `viewer.tsx` does, at `resetContent`. The
554
+ * test beside this pins both halves.
555
+ *
556
+ * `into` may be `view` or `up` — three.js `crossVectors` reads all six
557
+ * components before it writes any, and the intermediate is this module's own
558
+ * scratch, so writing the result over an input is safe. Said here because this
559
+ * is exported and an out-parameter invites the question; the test beside this
560
+ * pins it, so a three.js change cannot quietly turn the answer around.
561
+ *
562
+ * `view` and `up` being parallel is the one degenerate case: their cross is
563
+ * zero, `normalize` leaves it zero, and the result is a zero vector rather than
564
+ * an error. A caller with a free-orbiting camera reaches that at the pole,
565
+ * which is what `freeOrbit` and a squared `up` at every canonical pose exist to
566
+ * keep it away from.
567
+ */
568
+ declare function adaptedUp(view: Vector3, up: Vector3, into: Vector3): Vector3;
569
+ /** Named viewing directions, as unit vectors from the part toward the camera. */
570
+ declare const cadViewDirections: {
571
+ readonly front: Vector3;
572
+ readonly back: Vector3;
573
+ readonly left: Vector3;
574
+ readonly right: Vector3;
575
+ readonly top: Vector3;
576
+ readonly bottom: Vector3;
577
+ readonly isometric: Vector3;
578
+ };
579
+ type ViewerView = keyof typeof cadViewDirections;
580
+ /** The current orbit direction, so Fit can retain the direction being looked from. */
581
+ declare function currentViewDirection(camera: ViewerCamera, target: Vector3, into: Vector3): Vector3;
582
+
367
583
  /**
368
584
  * Mouse and trackpad presets.
369
585
  *
@@ -400,6 +616,20 @@ type ExtendedCameraControlsOptions = {
400
616
  declare class ExtendedCameraControls extends CameraControls {
401
617
  #private;
402
618
  constructor(camera: ViewerCamera, domElement: HTMLElement, options?: ExtendedCameraControlsOptions);
619
+ /**
620
+ * Bounds how far the wheel may travel, and where the orbit target may go.
621
+ *
622
+ * Re-applied whenever the scene is re-measured rather than set once: the
623
+ * limits are derived from the part's bounds, and a viewer that loads a second
624
+ * part would otherwise keep the first part's idea of far.
625
+ *
626
+ * The boundary confines the *target*. Zoom-to-cursor moves it, and goes on
627
+ * moving it after the zoom clamp bites — which is a runaway no zoom clamp can
628
+ * catch. `boundaryEnclosesCamera` stays off: under an orthographic camera the
629
+ * camera sits well outside the framing it is looking at, so enclosing it would
630
+ * drag the target back in on every frame.
631
+ */
632
+ applyLimits(limits: CameraLimits, boundary?: Box3): void;
403
633
  get freeOrbit(): boolean;
404
634
  get scheme(): ControlScheme;
405
635
  /** Adds the listeners this subclass owns, on top of the base connection. */
@@ -478,16 +708,33 @@ interface ViewerControls {
478
708
  }
479
709
  interface ViewerHandle extends ViewerControls {
480
710
  }
711
+ type VectorLike = Pick<Vector3, 'x' | 'y' | 'z'>;
481
712
 
482
713
  declare const useViewerControls: () => ViewerControls;
714
+ /** Re-aims the orbit, or `null` when this viewer has the gesture turned off. */
715
+ type Retarget = ((point: VectorLike) => void) | null;
716
+ /**
717
+ * How the part reports "orbit about this from now on".
718
+ *
719
+ * Split this way because the two halves belong to different places: only the
720
+ * part knows *where* the pointer landed, and it knows it from the raycast the
721
+ * selection already ran, so nothing here casts a second ray. Only the viewer
722
+ * knows whether the gesture is wanted, because `retargetOnDoubleClick` is its
723
+ * prop. `null` is that answer, and it turns the pairing off at the source
724
+ * rather than making the part detect a gesture nobody will act on.
725
+ */
726
+ declare const useRetarget: () => Retarget;
483
727
  interface ViewerProps {
484
728
  children?: ReactNode;
485
729
  className?: string;
486
730
  style?: CSSProperties;
487
731
  /**
488
- * Perspective by default. Orthographic is what a machinist reads a part in
489
- * parallel edges stay parallel, so two features the same size measure the
490
- * same size wherever they sit.
732
+ * **Orthographic by default.** It is what a machinist reads a part in:
733
+ * parallel edges stay parallel, so a wall that looks square is square, and
734
+ * two features the same size measure the same size wherever they sit.
735
+ *
736
+ * Perspective is the better answer for reading a deep pocket *as* depth, and
737
+ * is one prop away.
491
738
  */
492
739
  projection?: Projection;
493
740
  /**
@@ -512,6 +759,43 @@ interface ViewerProps {
512
759
  * makes easy to do.
513
760
  */
514
761
  recentreOnDoubleClick?: boolean;
762
+ /**
763
+ * Whether a double **left** click on the part orbits about what was clicked
764
+ * from then on. On by default.
765
+ *
766
+ * The clicked point moves to the middle of the view at the same size and
767
+ * angle, and stays the pivot until something else moves it. It is how you get
768
+ * from "the whole part" to "this corner" without losing the ability to turn
769
+ * what you are looking at — and under an orthographic camera it is the only
770
+ * gesture that re-aims the pivot at all, because the wheel there scales a
771
+ * frustum rather than travelling toward anything.
772
+ *
773
+ * The move is immediate rather than eased: the damping these controls ship
774
+ * with settles a transition inside one frame. Turn on {@link showOrbitTarget}
775
+ * if the pivot moving needs to be visible.
776
+ *
777
+ * The click is still a click. Its pick arrives with `doubled: true` on it, so
778
+ * an app that wants a double click to mean something of its own can say so,
779
+ * and one that would rather a double click left the selection alone can
780
+ * ignore it. What the viewport claims is the *view*, not the pick.
781
+ */
782
+ retargetOnDoubleClick?: boolean;
783
+ /**
784
+ * Whether to show two small circles at the orbit target — the point the view
785
+ * turns and zooms about. **Off by default.**
786
+ *
787
+ * They are up while a gesture is running and flash when the pivot moves on
788
+ * its own: a cursor zoom walking it, a double click re-aiming it, a Fit
789
+ * putting it back. Then they fade. It answers "why did the part swing that
790
+ * way", which nothing else on screen does, and it is what makes a wheel that
791
+ * has carried the pivot off the part legible while it is happening rather
792
+ * than after.
793
+ *
794
+ * Off by default because it is an aid rather than furniture: a viewer that
795
+ * grew a dot in the middle of every screenshot would be a surprise to anybody
796
+ * already rendering with this.
797
+ */
798
+ showOrbitTarget?: boolean;
515
799
  /** Lighting and background. The part's own colours are tuned against this rig. */
516
800
  theme?: Partial<ViewerTheme>;
517
801
  /**
@@ -684,4 +968,117 @@ declare function bestOwner(model: PartModel, owners: readonly FeatureTag[], cont
684
968
  */
685
969
  declare function cycleOwner(owners: readonly FeatureTag[], current: FeatureTag | null): FeatureTag | null;
686
970
 
687
- export { type ArrowPlacement, Axes, type AxesProps, CHAMFER, CadCameraControls, type CadCameraControlsProps, type ControlScheme, type CubeZone, DOUBLE_TAP_MS, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, type DoubleTapPoint, type DoubleTapTracker, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, type NamedDirection, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, Projection, type RankingContext, RegionIndex, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, ViewerView, arrowPlacement, bestOwner, buildRegionIndex, cubeOutlineGeometry, cubeZones, cycleOwner, directionIndexOf, directionLabel, featureTypeRank, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, movedFar, panelGeometry, rankOwners, regionAdjacency, regionEdgesGeometry, regionNormals, sameDirection, squaredUp, trackDoubleTaps, trackTaps, useContentBox, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
971
+ /** A camera and the point it orbits, together, because neither moves alone. */
972
+ interface RetargetPose {
973
+ readonly position: Vec3;
974
+ readonly target: Vec3;
975
+ }
976
+ /**
977
+ * Where the camera and its orbit target go when somebody asks to orbit about a
978
+ * point on the part.
979
+ *
980
+ * The target moves to the point and the camera moves by the same delta, so the
981
+ * offset between them — the viewing direction and the distance along it — comes
982
+ * through untouched. Only the pivot changes: what was clicked ends up in the
983
+ * middle of the view, at the same size, seen from the same angle, and every
984
+ * orbit and zoom afterwards is about it. This says where the pose lands, not
985
+ * how it gets there — how fast is the caller's transition and the controls'
986
+ * damping, and in this viewer that is one frame.
987
+ *
988
+ * Moving the target alone would swing the camera to look at the new point from
989
+ * the old place, which reads as the part being knocked sideways. Moving the
990
+ * camera alone would not change what anything orbits about, which is the whole
991
+ * request.
992
+ *
993
+ * This is what makes an orthographic viewport navigable. Under a perspective
994
+ * camera the wheel travels toward the target and a mis-aimed pivot is something
995
+ * you can dolly out of; under an orthographic one the wheel scales a frustum,
996
+ * so nothing about zooming ever re-aims the pivot, and F9 measured a cursor
997
+ * zoom leaving it in space off the part's corner.
998
+ *
999
+ * Pure, so the arithmetic is testable without a scene: three vectors in, two
1000
+ * out, no camera and no controls.
1001
+ */
1002
+ declare function retargetPose(cameraPosition: Vec3, currentTarget: Vec3, hitPoint: Vec3): RetargetPose;
1003
+
1004
+ /**
1005
+ * The orbit target made briefly visible.
1006
+ *
1007
+ * The pivot is the one thing the viewport moves about and never shows. It is
1008
+ * also the one thing that moves without being dragged: zooming to the cursor
1009
+ * walks it, a double click re-aims it, Fit puts it back. So "why did the part
1010
+ * swing that way" has an answer the screen has never given.
1011
+ *
1012
+ * It is an aid rather than furniture, which is why it is measured in time as
1013
+ * well as in pixels — up while a gesture is running, a flash when the pivot
1014
+ * moves on its own, and gone otherwise. A marker that stayed would be a dot in
1015
+ * the middle of every screenshot.
1016
+ */
1017
+ /** Radius of the solid centre dot, in CSS pixels. */
1018
+ declare const ORBIT_TARGET_PIXELS = 3;
1019
+ /** Outer radius of the ring around it, in CSS pixels. */
1020
+ declare const ORBIT_TARGET_RING_PIXELS = 14;
1021
+ /** The ring's thickness, as a fraction of its outer radius. */
1022
+ declare const ORBIT_TARGET_RING_WIDTH = 0.16;
1023
+ /**
1024
+ * A dot inside a ring, rather than the legacy viewer's two nested spheres.
1025
+ *
1026
+ * Legacy paired a depth-tested outer ball with a dot drawn over everything, so
1027
+ * the part burying the ball said the pivot was inside the material
1028
+ * (`use-target.ts:10-25`). Rendered against a real part that reads as a fault
1029
+ * rather than as a signal: a pivot sitting *on* a surface — which is exactly
1030
+ * what a double-click re-target produces, and the commonest case there is —
1031
+ * cuts the sphere in half, leaving a lens-shaped smudge with the dot off to one
1032
+ * side of it. The depth it was reporting is a question almost nobody asks; that
1033
+ * it looked broken is something everybody would see.
1034
+ *
1035
+ * A billboarded ring is the same marker with the ambiguity taken out. It is the
1036
+ * same shape at every pose, over any surface, and it is what a viewport
1037
+ * reticle looks like everywhere else.
1038
+ */
1039
+ declare const ORBIT_TARGET_COLOR = 3948625;
1040
+ declare const ORBIT_TARGET_RING_COLOR = 7057587;
1041
+ /** A ring is a hint about where, not a second dot. */
1042
+ declare const ORBIT_TARGET_RING_OPACITY = 0.9;
1043
+ /**
1044
+ * Both colours are the package's own rather than {@link ViewerTheme} roles.
1045
+ *
1046
+ * Adding a required field to that interface is a breaking type change for
1047
+ * anybody who builds a whole theme by hand, which is a steep price for an aid
1048
+ * that is off by default and on screen for about a second at a time. They are
1049
+ * palette colours already in use — the slate the cube labels and the section
1050
+ * handle outline take, and the teal that outlines a section cut — so the marker
1051
+ * reads as part of the control family rather than as something imported. If a
1052
+ * consumer asks to re-colour it, the roles go in at the next major and nothing
1053
+ * about this module's shape changes.
1054
+ */
1055
+ /**
1056
+ * How long the marker stays at full strength after the pivot moves on its own,
1057
+ * in milliseconds. Legacy's (`use-target.ts:96`).
1058
+ */
1059
+ declare const ORBIT_TARGET_FLASH_MS = 100;
1060
+ /**
1061
+ * How long it takes to fade once nothing is holding it up, in milliseconds.
1062
+ *
1063
+ * Legacy faded in twenty steps of 0.05 on a 50 ms timer, which is this number
1064
+ * arrived at the long way round (`use-target.ts:79-87`). Long enough that
1065
+ * letting go of an orbit does not make the marker vanish before the eye finds
1066
+ * it, short enough that it is gone before anybody wonders what it is.
1067
+ */
1068
+ declare const ORBIT_TARGET_FADE_MS = 1000;
1069
+ /**
1070
+ * How visible the marker is, `sinceHold` milliseconds after the last thing
1071
+ * holding it up let go.
1072
+ *
1073
+ * Linear, and negative input means something still is — a drag in progress, or
1074
+ * a flash inside its window — so the caller can pass one number rather than
1075
+ * branching on which of the two it is.
1076
+ *
1077
+ * Pure because the fade has to be driven from a frame rather than a timer:
1078
+ * this viewer renders on demand, so a `setInterval` writing an opacity would
1079
+ * change the material and never repaint it. The curve therefore has to be
1080
+ * something a frame can ask for the value of, at whatever moment it runs.
1081
+ */
1082
+ declare function orbitTargetOpacity(sinceHold: number, fade?: number): number;
1083
+
1084
+ export { type ArrowPlacement, Axes, type AxesProps, CAD_CAMERA_UP, CHAMFER, CadCameraControls, type CadCameraControlsProps, type CameraLimits, type ControlScheme, type CubeZone, DEFAULT_FIT_MARGIN, DOUBLE_TAP_MS, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, type DoubleTapPoint, type DoubleTapTracker, EXCLUDE_FROM_FRAME, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, MAX_FRAME_RATIO, MIN_FRAME_RATIO, type NamedDirection, ORBIT_TARGET_COLOR, ORBIT_TARGET_FADE_MS, ORBIT_TARGET_FLASH_MS, ORBIT_TARGET_PIXELS, ORBIT_TARGET_RING_COLOR, ORBIT_TARGET_RING_OPACITY, ORBIT_TARGET_RING_PIXELS, ORBIT_TARGET_RING_WIDTH, PERSPECTIVE_FOV, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, type Projection, type RankingContext, RegionIndex, type Retarget, type RetargetPose, type SceneBounds, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, type ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, type ViewerView, type ViewportSize, adaptedUp, applyProjection, arrowPlacement, aspectRatio, bestOwner, boundsFromBox, buildRegionIndex, cadViewDirections, cameraLimits, contentBounds, cubeOutlineGeometry, cubeZones, currentViewDirection, cycleOwner, defaultBounds, directionIndexOf, directionLabel, featureTypeRank, fitDistance, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, movedFar, orbitTargetOpacity, orthographicHalfHeight, panelGeometry, perspectiveFitDistance, rankOwners, regionAdjacency, regionEdgesGeometry, regionNormals, retargetPose, sameDirection, screenLength, squaredUp, startPosition, targetBoundary, trackDoubleTaps, trackTaps, useContentBox, useRetarget, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
package/dist/index.js CHANGED
@@ -18,7 +18,17 @@ import {
18
18
  HIGHLIGHT_COLORS,
19
19
  HIGHLIGHT_WEIGHT,
20
20
  HOVER_WEIGHT,
21
+ MAX_FRAME_RATIO,
22
+ MIN_FRAME_RATIO,
21
23
  NO_MODIFIERS,
24
+ ORBIT_TARGET_COLOR,
25
+ ORBIT_TARGET_FADE_MS,
26
+ ORBIT_TARGET_FLASH_MS,
27
+ ORBIT_TARGET_PIXELS,
28
+ ORBIT_TARGET_RING_COLOR,
29
+ ORBIT_TARGET_RING_OPACITY,
30
+ ORBIT_TARGET_RING_PIXELS,
31
+ ORBIT_TARGET_RING_WIDTH,
22
32
  PERSPECTIVE_FOV,
23
33
  PICKED_SURFACE_LABEL,
24
34
  PartMesh,
@@ -32,6 +42,7 @@ import {
32
42
  VIEW_NAMES,
33
43
  VIEW_SIGNS,
34
44
  Viewer,
45
+ adaptedUp,
35
46
  applyHighlightLayers,
36
47
  applyProjection,
37
48
  arrowPlacement,
@@ -43,6 +54,7 @@ import {
43
54
  buildRegionIndex,
44
55
  buildRegionTexels,
45
56
  cadViewDirections,
57
+ cameraLimits,
46
58
  contentBounds,
47
59
  createPart,
48
60
  cubeOutlineGeometry,
@@ -62,6 +74,7 @@ import {
62
74
  labelTexture,
63
75
  movedFar,
64
76
  normalizePartReport,
77
+ orbitTargetOpacity,
65
78
  orthographicHalfHeight,
66
79
  panelGeometry,
67
80
  perspectiveFitDistance,
@@ -71,6 +84,7 @@ import {
71
84
  regionEdgesGeometry,
72
85
  resolveSectionPlane,
73
86
  resolveTheme,
87
+ retargetPose,
74
88
  sameDirection,
75
89
  screenLength,
76
90
  sectionBounds,
@@ -84,10 +98,12 @@ import {
84
98
  smoothRegionNormals,
85
99
  squaredUp,
86
100
  startPosition,
101
+ targetBoundary,
87
102
  themesEqual,
88
103
  trackDoubleTaps,
89
104
  trackTaps,
90
105
  useContentBox,
106
+ useRetarget,
91
107
  useTapGuard,
92
108
  useViewerControls,
93
109
  viewDirection,
@@ -95,7 +111,7 @@ import {
95
111
  viewUp,
96
112
  viewVector,
97
113
  visualSurfaces
98
- } from "./chunk-ILZ2KQUK.js";
114
+ } from "./chunk-4BU4QZCL.js";
99
115
 
100
116
  // src/primitives.tsx
101
117
  import { GizmoHelper } from "@react-three/drei";
@@ -388,7 +404,17 @@ export {
388
404
  HIGHLIGHT_COLORS,
389
405
  HIGHLIGHT_WEIGHT,
390
406
  HOVER_WEIGHT,
407
+ MAX_FRAME_RATIO,
408
+ MIN_FRAME_RATIO,
391
409
  NO_MODIFIERS,
410
+ ORBIT_TARGET_COLOR,
411
+ ORBIT_TARGET_FADE_MS,
412
+ ORBIT_TARGET_FLASH_MS,
413
+ ORBIT_TARGET_PIXELS,
414
+ ORBIT_TARGET_RING_COLOR,
415
+ ORBIT_TARGET_RING_OPACITY,
416
+ ORBIT_TARGET_RING_PIXELS,
417
+ ORBIT_TARGET_RING_WIDTH,
392
418
  PERSPECTIVE_FOV,
393
419
  PICKED_SURFACE_LABEL,
394
420
  PartMesh,
@@ -402,6 +428,7 @@ export {
402
428
  VIEW_SIGNS,
403
429
  ViewCube,
404
430
  Viewer,
431
+ adaptedUp,
405
432
  applyHighlightLayers,
406
433
  applyProjection,
407
434
  arrowPlacement,
@@ -413,6 +440,7 @@ export {
413
440
  buildRegionIndex,
414
441
  buildRegionTexels,
415
442
  cadViewDirections,
443
+ cameraLimits,
416
444
  contentBounds,
417
445
  createPart,
418
446
  cubeOutlineGeometry,
@@ -434,6 +462,7 @@ export {
434
462
  labelTexture,
435
463
  movedFar,
436
464
  normalizePartReport,
465
+ orbitTargetOpacity,
437
466
  orthographicHalfHeight,
438
467
  panelGeometry,
439
468
  perspectiveFitDistance,
@@ -444,6 +473,7 @@ export {
444
473
  regionNormals,
445
474
  resolveSectionPlane,
446
475
  resolveTheme,
476
+ retargetPose,
447
477
  sameDirection,
448
478
  screenLength,
449
479
  sectionBounds,
@@ -457,10 +487,12 @@ export {
457
487
  smoothRegionNormals,
458
488
  squaredUp,
459
489
  startPosition,
490
+ targetBoundary,
460
491
  themesEqual,
461
492
  trackDoubleTaps,
462
493
  trackTaps,
463
494
  useContentBox,
495
+ useRetarget,
464
496
  useTapGuard,
465
497
  useViewerControls,
466
498
  viewDirection,