@toolpath/viewer 0.3.1 → 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.
- package/README.md +12 -2
- package/dist/{chunk-RICG6UVH.js → chunk-4BU4QZCL.js} +921 -202
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/index.d.ts +491 -12
- package/dist/index.js +60 -231
- package/dist/{normalize-Bzzwwxt4.d.ts → normalize-DsoIyM8B.d.ts} +37 -95
- package/package.json +7 -3
package/dist/engine/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PartModel, a as PartMeshRefs } from '../normalize-
|
|
2
|
-
export { E as EnginePart, b as EnginePartProps, M as MIN_KERNEL_VERSION, c as assertSupportedKernelVersion, n as normalizePartReport, s as smoothRegionNormals } from '../normalize-
|
|
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
|
|
package/dist/engine/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { B as BuildPickInput, C as
|
|
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';
|
|
3
4
|
import * as react from 'react';
|
|
4
5
|
import { RefObject, ReactNode, CSSProperties } from 'react';
|
|
5
|
-
import { BufferGeometry, CanvasTexture, Vector3, Box3 } from 'three';
|
|
6
6
|
import CameraControls from 'camera-controls';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Every face's neighbours, keyed by region index.
|
|
10
|
+
*
|
|
11
|
+
* One pass over the triangles: each contributes three edges, each edge names
|
|
12
|
+
* the region that owns it, and any edge named by two regions joins them.
|
|
13
|
+
*/
|
|
14
|
+
declare function regionAdjacency(model: PartModel, geometry: BufferGeometry): Map<number, Set<number>>;
|
|
15
|
+
|
|
8
16
|
/**
|
|
9
17
|
* The 26 standard views: the six faces of the cube, the twelve edges between
|
|
10
18
|
* them, and its eight corners.
|
|
@@ -39,6 +47,21 @@ declare function viewVector(name: ViewName): Vec3;
|
|
|
39
47
|
* come from here.
|
|
40
48
|
*/
|
|
41
49
|
declare function viewUp(direction: Vec3): Vec3;
|
|
50
|
+
/**
|
|
51
|
+
* The squared up vector nearest to the one the camera already has.
|
|
52
|
+
*
|
|
53
|
+
* A view has four square orientations, not one: {@link viewUp} rolled by 0°,
|
|
54
|
+
* 90°, 180° and 270° about the view direction. Snapping to the canonical one
|
|
55
|
+
* would spin the part on the way to a view somebody asked for because it was
|
|
56
|
+
* already nearly in front of them — arrive at the bottom view from the right
|
|
57
|
+
* and the part turns a quarter of a turn for no reason they can see.
|
|
58
|
+
*
|
|
59
|
+
* So the roll is chosen, not imposed: of the four, the one closest to where the
|
|
60
|
+
* camera is looking from now. The view still lands square — Bottom simply lands
|
|
61
|
+
* square in whichever of its four ways was already nearest, which is how the
|
|
62
|
+
* Fusion cube behaves and what somebody arriving from it will expect.
|
|
63
|
+
*/
|
|
64
|
+
declare function squaredUp(direction: Vec3, currentUp: Vec3): Vec3;
|
|
42
65
|
/**
|
|
43
66
|
* Half-width of a face panel, where the cube's half-extent is 1 — so the
|
|
44
67
|
* chamfer taken off each edge is the remaining `1 - CHAMFER`.
|
|
@@ -134,10 +157,15 @@ interface DirectionArrowsProps {
|
|
|
134
157
|
*/
|
|
135
158
|
activeDirection?: number | null;
|
|
136
159
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
160
|
+
* Which arrows are drawn, without scoping anything: `null` for all of them,
|
|
161
|
+
* an index for one, `-1` for none — and **a list of indices for a set**, which
|
|
162
|
+
* is how a plan draws only the ways up it has confirmed.
|
|
163
|
+
*
|
|
164
|
+
* One prop rather than a singular and a plural, because two would need a rule
|
|
165
|
+
* about which wins, and a caller with an answer to give should not also have
|
|
166
|
+
* to say where to put it. Falls back to `activeDirection`.
|
|
139
167
|
*/
|
|
140
|
-
shownDirection?: number | null;
|
|
168
|
+
shownDirection?: number | readonly number[] | null;
|
|
141
169
|
/**
|
|
142
170
|
* A direction being named, drawn while it is aimed. Not a candidate and not a
|
|
143
171
|
* selection: a way up that does not exist yet, so it is drawn over the part —
|
|
@@ -227,14 +255,67 @@ interface TapTracker {
|
|
|
227
255
|
* and a listener that runs after `stopPropagation` runs never.
|
|
228
256
|
*/
|
|
229
257
|
declare function trackTaps(element: HTMLElement): TapTracker;
|
|
258
|
+
/**
|
|
259
|
+
* How long between two presses still reads as one gesture, in milliseconds.
|
|
260
|
+
*
|
|
261
|
+
* The browser's own `dblclick` threshold is a platform setting and is not
|
|
262
|
+
* readable from script, so a hand-paired gesture has to pick a number. 400ms is
|
|
263
|
+
* inside every default and outside a deliberate pair of separate clicks.
|
|
264
|
+
*/
|
|
265
|
+
declare const DOUBLE_TAP_MS = 400;
|
|
266
|
+
interface DoubleTapPoint extends TapPoint {
|
|
267
|
+
readonly timeStamp: number;
|
|
268
|
+
}
|
|
269
|
+
interface DoubleTapTracker {
|
|
270
|
+
/** Whether this press completes a double tap begun by the last one. */
|
|
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;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Pairing presses into a double tap by hand.
|
|
284
|
+
*
|
|
285
|
+
* Needed because `dblclick` fires for the **primary button only** — there is no
|
|
286
|
+
* such event for the middle button, however many times it is pressed. So a
|
|
287
|
+
* middle-button gesture has to be assembled from single presses.
|
|
288
|
+
*
|
|
289
|
+
* Position matters as well as time: two presses at opposite corners inside the
|
|
290
|
+
* window are two clicks that happened to be quick, not one gesture, and
|
|
291
|
+
* treating them as a double would re-frame the view out from under somebody
|
|
292
|
+
* who was doing something else. The same {@link TAP_SLOP} the click guard uses.
|
|
293
|
+
*
|
|
294
|
+
* A completed double clears the state rather than leaving it, so three presses
|
|
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.
|
|
302
|
+
*/
|
|
303
|
+
declare function trackDoubleTaps(within?: number, slop?: number): DoubleTapTracker;
|
|
230
304
|
|
|
305
|
+
/** Whether the gesture ending at this event was a click rather than a drag. */
|
|
306
|
+
type TapGuard = (event: TapPoint) => boolean;
|
|
231
307
|
/**
|
|
232
308
|
* A click that is a click, for anything inside the canvas.
|
|
233
309
|
*
|
|
234
310
|
* Bound to the canvas element rather than to the object, because the gesture
|
|
235
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.
|
|
236
317
|
*/
|
|
237
|
-
declare function useTapGuard():
|
|
318
|
+
declare function useTapGuard(): TapGuard;
|
|
238
319
|
|
|
239
320
|
interface GridSpec {
|
|
240
321
|
/** Cell size in part units (millimetres). */
|
|
@@ -305,6 +386,200 @@ type SurfaceOf = ReadonlyMap<number, number>;
|
|
|
305
386
|
*/
|
|
306
387
|
declare function visualSurfaces(regions: readonly PartModelRegion[]): SurfaceOf;
|
|
307
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
|
+
|
|
308
583
|
/**
|
|
309
584
|
* Mouse and trackpad presets.
|
|
310
585
|
*
|
|
@@ -341,6 +616,20 @@ type ExtendedCameraControlsOptions = {
|
|
|
341
616
|
declare class ExtendedCameraControls extends CameraControls {
|
|
342
617
|
#private;
|
|
343
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;
|
|
344
633
|
get freeOrbit(): boolean;
|
|
345
634
|
get scheme(): ControlScheme;
|
|
346
635
|
/** Adds the listeners this subclass owns, on top of the base connection. */
|
|
@@ -368,6 +657,15 @@ interface CadCameraControlsProps {
|
|
|
368
657
|
* about an orbit control.
|
|
369
658
|
*/
|
|
370
659
|
freeOrbit?: boolean;
|
|
660
|
+
/**
|
|
661
|
+
* What the wheel zooms toward: the pointer, or the middle of the view.
|
|
662
|
+
*
|
|
663
|
+
* Fusion and SolidWorks zoom to the cursor and most people expect it — you
|
|
664
|
+
* point at the corner you want and lean in. It is not universally liked: on a
|
|
665
|
+
* trackpad it can walk the model off screen, which is why the other one
|
|
666
|
+
* stays.
|
|
667
|
+
*/
|
|
668
|
+
zoomTo?: 'cursor' | 'centre';
|
|
371
669
|
}
|
|
372
670
|
/**
|
|
373
671
|
* Mounts `camera-controls` against the R3F camera and canvas.
|
|
@@ -378,7 +676,7 @@ interface CadCameraControlsProps {
|
|
|
378
676
|
* also registered as R3F's default `controls`, which is how anything else in
|
|
379
677
|
* the scene reaches the orbit target.
|
|
380
678
|
*/
|
|
381
|
-
declare const CadCameraControls: ({ controlsRef, scheme, freeOrbit, }: CadCameraControlsProps) => null;
|
|
679
|
+
declare const CadCameraControls: ({ controlsRef, scheme, freeOrbit, zoomTo, }: CadCameraControlsProps) => null;
|
|
382
680
|
|
|
383
681
|
interface ViewerControls {
|
|
384
682
|
/** Frames the part without changing the current viewing direction. */
|
|
@@ -410,16 +708,33 @@ interface ViewerControls {
|
|
|
410
708
|
}
|
|
411
709
|
interface ViewerHandle extends ViewerControls {
|
|
412
710
|
}
|
|
711
|
+
type VectorLike = Pick<Vector3, 'x' | 'y' | 'z'>;
|
|
413
712
|
|
|
414
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;
|
|
415
727
|
interface ViewerProps {
|
|
416
728
|
children?: ReactNode;
|
|
417
729
|
className?: string;
|
|
418
730
|
style?: CSSProperties;
|
|
419
731
|
/**
|
|
420
|
-
*
|
|
421
|
-
* parallel edges stay parallel, so
|
|
422
|
-
* 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.
|
|
423
738
|
*/
|
|
424
739
|
projection?: Projection;
|
|
425
740
|
/**
|
|
@@ -430,6 +745,57 @@ interface ViewerProps {
|
|
|
430
745
|
controls?: ControlScheme;
|
|
431
746
|
/** Orbit past the poles instead of stopping there. On by default. */
|
|
432
747
|
freeOrbit?: boolean;
|
|
748
|
+
/**
|
|
749
|
+
* What the wheel zooms toward: the pointer, or the middle of the view.
|
|
750
|
+
*
|
|
751
|
+
* `cursor` by default, which is what Fusion does and what most people reach
|
|
752
|
+
* for. It is a preference rather than a right answer — on a trackpad it can
|
|
753
|
+
* walk the model off screen.
|
|
754
|
+
*/
|
|
755
|
+
zoomTo?: 'cursor' | 'centre';
|
|
756
|
+
/**
|
|
757
|
+
* Whether a double **middle** click re-frames the part. On by default — it is
|
|
758
|
+
* the way back from having zoomed into a corner, which zooming to the cursor
|
|
759
|
+
* makes easy to do.
|
|
760
|
+
*/
|
|
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;
|
|
433
799
|
/** Lighting and background. The part's own colours are tuned against this rig. */
|
|
434
800
|
theme?: Partial<ViewerTheme>;
|
|
435
801
|
/**
|
|
@@ -602,4 +968,117 @@ declare function bestOwner(model: PartModel, owners: readonly FeatureTag[], cont
|
|
|
602
968
|
*/
|
|
603
969
|
declare function cycleOwner(owners: readonly FeatureTag[], current: FeatureTag | null): FeatureTag | null;
|
|
604
970
|
|
|
605
|
-
|
|
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 };
|