@toolpath/viewer 1.1.1 → 1.3.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 +873 -94
- package/dist/{chunk-4BU4QZCL.js → chunk-HWAEUJ4H.js} +636 -277
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/index.d.ts +680 -198
- package/dist/index.js +1281 -1
- package/dist/{normalize-OULo7e0F.d.ts → normalize-DHOSNiuO.d.ts} +221 -139
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { P as PartModel, V as Vec3, d as ViewerTheme, e as PartModelRegion, T as TriangleRange, F as FeatureTag, R as RegionIndex,
|
|
2
|
-
export { B as BuildPickInput, C as CANDIDATE_WEIGHT, D as DEFAULT_THEME,
|
|
3
|
-
import { BufferGeometry, CanvasTexture,
|
|
1
|
+
import { P as PartModel, V as Vec3, d as ViewerTheme, S as SectionOptions, e as SectionState, f as PartModelRegion, T as TriangleRange, F as FeatureTag, R as RegionIndex, g as PartModelFeature, h as FeatureType } from './normalize-DHOSNiuO.js';
|
|
2
|
+
export { A as AXES_PLANE_OFFSET, i as AXES_PLANE_SCALE, j as AxesPlane, B as BuildPickInput, C as CANDIDATE_WEIGHT, k as CAP_HATCH, D as DEFAULT_SECTION_NORMAL, l as DEFAULT_THEME, m as DIRECTION_COLORS, o as DISABLED_SECTION, 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, v as KnownShapeKind, N as NO_MODIFIERS, O as OUTLINE_SCALE, w as PICKED_SURFACE_LABEL, x as PREVIEW_SCALE, y as PartMesh, z as PartMeshProps, a as PartMeshRefs, G as PartObject, I as PartPick, J as PickModifiers, L as REGION_ATTRIBUTE, Q as RegionHighlight, U as RegionPaint, W as SECTION_RENDER_ORDER, X as SectionAnchor, Y as SectionBounds, Z as SectionPlacement, _ as ShapeKind, $ as SurfaceHit, a0 as applyHighlightLayers, a1 as axesPlanes, a2 as buildPick, a3 as buildRegionAttribute, a4 as buildRegionTexels, a5 as createPart, a6 as directionColor, a7 as dragPlane, a8 as focusForPick, a9 as hitUnderRay, n as normalizePartReport, aa as pickedStartDepth, ab as resolveTheme, ac as sectionAnchor, ad as sectionBounds, ae as sectionConstant, af as sectionDepth, ag as sectionDepthConstant, ah as sectionDepthRange, ai as sectionFromPick, aj as sectionOffset, ak as sectionOptionsFromState, al as sectionPlane, s as smoothRegionNormals, am as surfaceUnderRay, an as themesEqual, ao as viewDirection } from './normalize-DHOSNiuO.js';
|
|
3
|
+
import { BufferGeometry, CanvasTexture, Box3, Plane, Vector3, OrthographicCamera, PerspectiveCamera, Object3D, Matrix4, Vector2, Ray, ShaderMaterial, Texture, Color, MeshBasicMaterial, WebGLRenderTarget } from 'three';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { RefObject, ReactNode, CSSProperties } from 'react';
|
|
6
6
|
import CameraControls from 'camera-controls';
|
|
@@ -192,6 +192,668 @@ interface DirectionArrowsProps {
|
|
|
192
192
|
*/
|
|
193
193
|
declare const DirectionArrows: ({ directions, activeDirection, shownDirection, previewDirection, namedDirections, onPickDirection, theme, visible, }: DirectionArrowsProps) => react.JSX.Element | null;
|
|
194
194
|
|
|
195
|
+
/** The cut's plane for a set of options, or `null` when there is no cut. */
|
|
196
|
+
declare function resolveSectionPlane(options: SectionOptions | undefined, box: Box3): {
|
|
197
|
+
plane: Plane;
|
|
198
|
+
state: SectionState;
|
|
199
|
+
} | null;
|
|
200
|
+
interface SectionViewProps {
|
|
201
|
+
geometry: BufferGeometry;
|
|
202
|
+
box: Box3;
|
|
203
|
+
plane: Plane;
|
|
204
|
+
theme: ViewerTheme;
|
|
205
|
+
showHandle: boolean;
|
|
206
|
+
onDrag?: (constant: number) => void;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* A clipping plane with a hatched cap over the cut, and an arrow that drags it.
|
|
210
|
+
*
|
|
211
|
+
* The cap is the standard two-pass stencil trick: draw the clipped geometry's
|
|
212
|
+
* back faces incrementing the stencil and its front faces decrementing it, so a
|
|
213
|
+
* non-zero stencil marks exactly where the plane passes through solid material,
|
|
214
|
+
* then fill that region with a quad. Without it a section shows the inside of
|
|
215
|
+
* the far wall and the part reads as hollow.
|
|
216
|
+
*
|
|
217
|
+
* The fill is hatched and outlined — see `render/section-cap.ts` — which takes
|
|
218
|
+
* a pre-pass: the same stencil trick is run once more into a render target on
|
|
219
|
+
* its own, so the cap's shader can find its edge by sampling that mask. That
|
|
220
|
+
* pass draws a scene of its own holding nothing but the stencil meshes and the
|
|
221
|
+
* mask quad, at a priority ahead of R3F's own render.
|
|
222
|
+
*
|
|
223
|
+
* **The renderer must be created with `stencil: true`.** three defaults it to
|
|
224
|
+
* false, and without it every one of those stencil operations is a silent
|
|
225
|
+
* no-op — the cut still happens and the part still looks hollow, which is a
|
|
226
|
+
* confusing way to find out.
|
|
227
|
+
*/
|
|
228
|
+
declare const SectionView: ({ geometry, box, plane, theme, showHandle, onDrag, }: SectionViewProps) => react.JSX.Element;
|
|
229
|
+
|
|
230
|
+
interface SectionToolProps {
|
|
231
|
+
theme?: Partial<ViewerTheme>;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The interactive way to cut a part open. Mount it beside `<PartMesh>`.
|
|
235
|
+
*
|
|
236
|
+
* With no cut in place it offers two: hover a face and a small square previews
|
|
237
|
+
* a cut through it — click to make it — or click one of the three global
|
|
238
|
+
* planes standing behind the part to sweep along that axis. Once there is a
|
|
239
|
+
* cut it draws the cutting plane as a translucent sheet with an outline, and
|
|
240
|
+
* `Escape` clears it. The handle that drags the cut, and the hatched cap, are
|
|
241
|
+
* the part's own and appear whether or not this is mounted.
|
|
242
|
+
*
|
|
243
|
+
* Nothing needs wiring. The cut lives in the viewer, `<PartMesh>` follows it
|
|
244
|
+
* when it is given no `section` of its own, and `ViewerHandle.setSection` is
|
|
245
|
+
* the way to set or clear it from outside the canvas. While this is mounted
|
|
246
|
+
* the part reports no hovers or picks: a click on a face cuts through it
|
|
247
|
+
* without also selecting it, and a cut part is looked at rather than picked
|
|
248
|
+
* at. Unmounting it hands the pointer back.
|
|
249
|
+
*/
|
|
250
|
+
declare const SectionTool: ({ theme }: SectionToolProps) => react.JSX.Element | null;
|
|
251
|
+
|
|
252
|
+
type Projection = 'orthographic' | 'perspective';
|
|
253
|
+
type ViewerCamera = OrthographicCamera | PerspectiveCamera;
|
|
254
|
+
interface ViewportSize {
|
|
255
|
+
readonly width: number;
|
|
256
|
+
readonly height: number;
|
|
257
|
+
}
|
|
258
|
+
/** Vertical field of view, in degrees, for the perspective camera. */
|
|
259
|
+
declare const PERSPECTIVE_FOV = 30;
|
|
260
|
+
/** Padding around the framed bounds, as a multiple of its radius. */
|
|
261
|
+
declare const DEFAULT_FIT_MARGIN = 1.2;
|
|
262
|
+
/** Marks scene furniture — grid, axes — that the camera should not frame. */
|
|
263
|
+
declare const EXCLUDE_FROM_FRAME = "viewerExcludeFromFrame";
|
|
264
|
+
/**
|
|
265
|
+
* What the camera frames: a bounding *sphere*, not a box.
|
|
266
|
+
*
|
|
267
|
+
* A sphere makes framing rotation-invariant — the part stays fully visible from
|
|
268
|
+
* every angle, and a resize never needs to know the current pose. Framing
|
|
269
|
+
* per-axis box dimensions instead means recomputing the camera distance on
|
|
270
|
+
* every resize, and still clipping on some orientations.
|
|
271
|
+
*/
|
|
272
|
+
interface SceneBounds {
|
|
273
|
+
readonly center: Vector3;
|
|
274
|
+
readonly radius: number;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Bounds for a scene with nothing in it. A viewer is mounted before it has a
|
|
278
|
+
* part, and a camera with a zero-size frustum renders nothing at all.
|
|
279
|
+
*/
|
|
280
|
+
declare function defaultBounds(): SceneBounds;
|
|
281
|
+
/**
|
|
282
|
+
* A container can be laid out at zero size, and a degenerate aspect ratio
|
|
283
|
+
* produces a `NaN` projection matrix that never recovers. Fall back to square.
|
|
284
|
+
*/
|
|
285
|
+
declare function aspectRatio(size: ViewportSize): number;
|
|
286
|
+
/**
|
|
287
|
+
* A world length that covers `pixels` on screen at `point`.
|
|
288
|
+
*
|
|
289
|
+
* Anything sized in world units is a thumbnail on a plate and a wall on an
|
|
290
|
+
* insert; the whole reason a control is measured this way is that it is a
|
|
291
|
+
* control rather than part of the model. The section handle and the orbit
|
|
292
|
+
* target marker both size themselves with it, which is why it lives here with
|
|
293
|
+
* the camera rather than beside either of them.
|
|
294
|
+
*
|
|
295
|
+
* The two projections answer differently on purpose. An orthographic frustum
|
|
296
|
+
* covers the same world height wherever the point is, so distance does not
|
|
297
|
+
* enter; a perspective one covers more the further away it looks, so it does.
|
|
298
|
+
*/
|
|
299
|
+
declare function screenLength(camera: ViewerCamera, point: Vector3, viewport: ViewportSize, pixels: number): number;
|
|
300
|
+
declare function boundsFromBox(box: Box3): SceneBounds;
|
|
301
|
+
/**
|
|
302
|
+
* The bounds of everything worth framing under `root`.
|
|
303
|
+
*
|
|
304
|
+
* Skips objects flagged with {@link EXCLUDE_FROM_FRAME} and their descendants:
|
|
305
|
+
* a 100 mm grid around a 6 mm part would otherwise frame the grid, and the part
|
|
306
|
+
* would be a speck.
|
|
307
|
+
*/
|
|
308
|
+
declare function contentBounds(root: Object3D, into: Box3): SceneBounds;
|
|
309
|
+
/**
|
|
310
|
+
* Whether `object`, or anything between it and `root`, carries
|
|
311
|
+
* {@link EXCLUDE_FROM_FRAME}. The flag is set on an overlay's outermost group
|
|
312
|
+
* and means the whole subtree, so the walk goes up rather than reading the
|
|
313
|
+
* object alone.
|
|
314
|
+
*/
|
|
315
|
+
declare function excludedFromFrame(object: Object3D, root: Object3D): boolean;
|
|
316
|
+
/**
|
|
317
|
+
* The distance at which a sphere of `radius` fits inside a perspective frustum.
|
|
318
|
+
* Uses the narrower of the two field-of-view angles, so a portrait viewport
|
|
319
|
+
* frames on width and a landscape one on height.
|
|
320
|
+
*/
|
|
321
|
+
declare function perspectiveFitDistance(fovDegrees: number, aspect: number, radius: number): number;
|
|
322
|
+
/**
|
|
323
|
+
* Half-height of an orthographic frustum that fits a sphere of `radius`. A
|
|
324
|
+
* portrait viewport grows the height so the width still clears the sphere.
|
|
325
|
+
*/
|
|
326
|
+
declare function orthographicHalfHeight(aspect: number, radius: number): number;
|
|
327
|
+
declare function fitDistance(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): number;
|
|
328
|
+
declare function startPosition(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): Vector3;
|
|
329
|
+
/**
|
|
330
|
+
* How far out the wheel may pull, as a fraction of the fitted framing: the part
|
|
331
|
+
* may shrink to a quarter of the view and no further. Past that it is a speck
|
|
332
|
+
* with no cue that Fit is the way back.
|
|
333
|
+
*/
|
|
334
|
+
declare const MIN_FRAME_RATIO = 0.25;
|
|
335
|
+
/**
|
|
336
|
+
* How far in the wheel may push, as a multiple of the fitted framing.
|
|
337
|
+
*
|
|
338
|
+
* Both ratios are the legacy viewer's `minZoom` 0.25 / `maxZoom` 10
|
|
339
|
+
* (`three-object.tsx:289-311`), which transfer almost exactly: legacy's
|
|
340
|
+
* orthographic frustum was `largestDimension * 2` tall and this one is the
|
|
341
|
+
* fitted bounding sphere, which for a cube agree within a few percent. So the
|
|
342
|
+
* numbers are the ones that shipped, and here they carry a meaning they did not
|
|
343
|
+
* have there — `zoom` 1 *is* the fitted framing, so 0.25 and 10 say exactly
|
|
344
|
+
* what a reader thinks they say.
|
|
345
|
+
*/
|
|
346
|
+
declare const MAX_FRAME_RATIO = 10;
|
|
347
|
+
/** How far the camera may travel, in the two units the two cameras travel in. */
|
|
348
|
+
interface CameraLimits {
|
|
349
|
+
readonly minZoom: number;
|
|
350
|
+
readonly maxZoom: number;
|
|
351
|
+
readonly minDistance: number;
|
|
352
|
+
readonly maxDistance: number;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The clamps for a scene, so the wheel cannot leave the viewport empty.
|
|
356
|
+
*
|
|
357
|
+
* Without them it can, from either end, and both cameras can do it: an
|
|
358
|
+
* orthographic `camera.zoom` reaches 1e30 in sixty notches, and a perspective
|
|
359
|
+
* camera dives inside the part in eight, because `minDistance` defaults to
|
|
360
|
+
* `Number.EPSILON`. Fit recovers from both — but Fit is a double middle click
|
|
361
|
+
* and nothing on screen says so, which makes it a way out rather than an
|
|
362
|
+
* answer.
|
|
363
|
+
*
|
|
364
|
+
* One rule for both cameras: the wheel may take the part from a quarter of its
|
|
365
|
+
* fitted size to ten times it. Under an orthographic camera that scale *is* the
|
|
366
|
+
* frustum, so the ratios land on `zoom`; under a perspective camera apparent
|
|
367
|
+
* size is the inverse of distance, so they land on `distance` — which is why
|
|
368
|
+
* they swap sides between the two.
|
|
369
|
+
*
|
|
370
|
+
* Pure, and derived from `bounds`, so it re-derives wherever the scene is
|
|
371
|
+
* re-measured rather than being sampled once at mount.
|
|
372
|
+
*
|
|
373
|
+
* `framed` is what the view has been framed *on*, when that is not the whole
|
|
374
|
+
* scene — `frameBox` on a single feature. The band is then taken about that
|
|
375
|
+
* framing as well as about the scene's, because a band centred on the part is
|
|
376
|
+
* the wrong band for a view of a 3 mm hole in it: ten times the *part's* fitted
|
|
377
|
+
* size can be less than once the hole's, so the framing the caller asked for sat
|
|
378
|
+
* outside the clamps meant to keep it in view. Under an orthographic camera
|
|
379
|
+
* `zoom` is measured against a frustum still sized to the scene, so the framing
|
|
380
|
+
* enters as the zoom ratio it takes to reach; under a perspective one it enters
|
|
381
|
+
* as its own fitted distance.
|
|
382
|
+
*
|
|
383
|
+
* Widened to the union of the two rather than moved onto `framed`, and that is
|
|
384
|
+
* the whole of why this takes both. A band that simply followed the last framing
|
|
385
|
+
* would take the part away: framing a hole would put "far enough out to see what
|
|
386
|
+
* the hole is in" past `maxDistance`, so the wheel could no longer get back to
|
|
387
|
+
* the part it had just been looking at, and Fit — a gesture with nothing on
|
|
388
|
+
* screen to say so — would be the only way. Reaching further in must not cost
|
|
389
|
+
* the reach back out, so each end takes whichever bound is looser. With no
|
|
390
|
+
* `framed`, or one the size of the scene, both ends collapse to the scene's own
|
|
391
|
+
* and this is the function it was before.
|
|
392
|
+
*/
|
|
393
|
+
declare function cameraLimits(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number, framed?: SceneBounds): CameraLimits;
|
|
394
|
+
/**
|
|
395
|
+
* The box the orbit target is confined to — the fitted framing, several frames
|
|
396
|
+
* over. Written into `into` so the per-frame path allocates nothing.
|
|
397
|
+
*/
|
|
398
|
+
declare function targetBoundary(bounds: SceneBounds, into: Box3, margin?: number): Box3;
|
|
399
|
+
/**
|
|
400
|
+
* Points an existing camera's frustum at `bounds` for the current viewport.
|
|
401
|
+
* Pose is untouched — that belongs to the controls.
|
|
402
|
+
*/
|
|
403
|
+
declare function applyProjection(camera: ViewerCamera, size: ViewportSize, bounds: SceneBounds, margin?: number): void;
|
|
404
|
+
/**
|
|
405
|
+
* The world-up convention. The part data is Z-up (millimetres, no conversion) —
|
|
406
|
+
* not the glTF-conventional Y-up — so the camera has to say so explicitly.
|
|
407
|
+
*/
|
|
408
|
+
declare const CAD_CAMERA_UP: Vector3;
|
|
409
|
+
/**
|
|
410
|
+
* `up` re-squared against a view direction: the part of it perpendicular to
|
|
411
|
+
* `view`, written into `into`.
|
|
412
|
+
*
|
|
413
|
+
* This is what keeps the horizon level while a free orbit carries the camera
|
|
414
|
+
* over a pole — the up vector follows the view instead of flipping. The
|
|
415
|
+
* controls run it on every update, which is what makes the next sentence the
|
|
416
|
+
* important one.
|
|
417
|
+
*
|
|
418
|
+
* **It is path-dependent.** It is a projection, so it discards the component it
|
|
419
|
+
* removes and cannot put it back: running it at one view and then another does
|
|
420
|
+
* not give what running it once at the second view would. So a camera carries
|
|
421
|
+
* the roll of every pose it has passed through, and any pose that is meant to
|
|
422
|
+
* be canonical — the opening frame, a reset, a named view — has to say what
|
|
423
|
+
* `up` is rather than inherit it. `viewer.tsx` does, at `resetContent`. The
|
|
424
|
+
* test beside this pins both halves.
|
|
425
|
+
*
|
|
426
|
+
* `into` may be `view` or `up` — three.js `crossVectors` reads all six
|
|
427
|
+
* components before it writes any, and the intermediate is this module's own
|
|
428
|
+
* scratch, so writing the result over an input is safe. Said here because this
|
|
429
|
+
* is exported and an out-parameter invites the question; the test beside this
|
|
430
|
+
* pins it, so a three.js change cannot quietly turn the answer around.
|
|
431
|
+
*
|
|
432
|
+
* `view` and `up` being parallel is the one degenerate case: their cross is
|
|
433
|
+
* zero, `normalize` leaves it zero, and the result is a zero vector rather than
|
|
434
|
+
* an error. A caller with a free-orbiting camera reaches that at the pole,
|
|
435
|
+
* which is what `freeOrbit` and a squared `up` at every canonical pose exist to
|
|
436
|
+
* keep it away from.
|
|
437
|
+
*/
|
|
438
|
+
declare function adaptedUp(view: Vector3, up: Vector3, into: Vector3): Vector3;
|
|
439
|
+
/** Named viewing directions, as unit vectors from the part toward the camera. */
|
|
440
|
+
declare const cadViewDirections: {
|
|
441
|
+
readonly front: Vector3;
|
|
442
|
+
readonly back: Vector3;
|
|
443
|
+
readonly left: Vector3;
|
|
444
|
+
readonly right: Vector3;
|
|
445
|
+
readonly top: Vector3;
|
|
446
|
+
readonly bottom: Vector3;
|
|
447
|
+
readonly isometric: Vector3;
|
|
448
|
+
};
|
|
449
|
+
type ViewerView = keyof typeof cadViewDirections;
|
|
450
|
+
/** The current orbit direction, so Fit can retain the direction being looked from. */
|
|
451
|
+
declare function currentViewDirection(camera: ViewerCamera, target: Vector3, into: Vector3): Vector3;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Measuring, without a canvas: where a pointer snaps to, what two or three
|
|
455
|
+
* points measure, and how that is written down. `measure-tool.tsx` is the
|
|
456
|
+
* React surface over this.
|
|
457
|
+
*/
|
|
458
|
+
/** Draws over the part and the section cap, under nothing. */
|
|
459
|
+
declare const MEASURE_RENDER_ORDER = 7;
|
|
460
|
+
/**
|
|
461
|
+
* One colour per world axis, the hue `<Axes>` draws that axis in — lightened,
|
|
462
|
+
* because a pure primary through 30% on a dark page is mud, and the blue one
|
|
463
|
+
* was not there at all. The section tool's three global planes and a
|
|
464
|
+
* distance's X, Y, Z legs both wear them, so an axis is one colour everywhere.
|
|
465
|
+
*/
|
|
466
|
+
declare const AXIS_COLORS: Record<Axis, number>;
|
|
467
|
+
/**
|
|
468
|
+
* How close, in CSS pixels, the pointer has to be for a snap to take.
|
|
469
|
+
*
|
|
470
|
+
* A vertex or a midpoint is a point, so it gets the wider reach; an edge is a
|
|
471
|
+
* line the pointer can sit on, so it needs less. The pointer snaps to the
|
|
472
|
+
* strongest kind it is within reach of — a corner over the edge it ends, the
|
|
473
|
+
* edge over the face it bounds — which is what makes clicking the corner of a
|
|
474
|
+
* part produce the corner rather than a point a pixel off it.
|
|
475
|
+
*/
|
|
476
|
+
declare const SNAP_PIXELS: {
|
|
477
|
+
readonly vertex: 14;
|
|
478
|
+
readonly midpoint: 12;
|
|
479
|
+
readonly edge: 9;
|
|
480
|
+
};
|
|
481
|
+
/** The snap indicator's radius on screen, by what it is on, in CSS pixels. */
|
|
482
|
+
declare const SNAP_MARKER_PIXELS: {
|
|
483
|
+
readonly vertex: 6;
|
|
484
|
+
readonly midpoint: 5;
|
|
485
|
+
readonly edge: 4;
|
|
486
|
+
readonly face: 3.5;
|
|
487
|
+
};
|
|
488
|
+
/** A measurement's end marker, in CSS pixels. */
|
|
489
|
+
declare const MEASURE_MARKER_PIXELS = 3.5;
|
|
490
|
+
/**
|
|
491
|
+
* Line widths in CSS pixels: a measurement's own line, a delta leg, and the
|
|
492
|
+
* edge the pointer is snapped to.
|
|
493
|
+
*/
|
|
494
|
+
declare const MEASURE_LINE_PIXELS: {
|
|
495
|
+
readonly line: 2;
|
|
496
|
+
readonly leg: 1.5;
|
|
497
|
+
readonly edge: 3;
|
|
498
|
+
};
|
|
499
|
+
/** Dash and gap of a delta leg, in CSS pixels. */
|
|
500
|
+
declare const DELTA_DASH_PIXELS = 6;
|
|
501
|
+
/** How much of the shorter arm an angle's arc is drawn at. */
|
|
502
|
+
declare const ANGLE_ARC_FRACTION = 0.3;
|
|
503
|
+
/** Segments in an angle's arc. */
|
|
504
|
+
declare const ANGLE_ARC_SEGMENTS = 24;
|
|
505
|
+
type SnapKind = 'vertex' | 'midpoint' | 'edge' | 'face';
|
|
506
|
+
/** Where the pointer landed after snapping, and what it landed on. */
|
|
507
|
+
interface Snap {
|
|
508
|
+
readonly kind: SnapKind;
|
|
509
|
+
readonly point: Vector3;
|
|
510
|
+
/** The outward normal of the surface under the pointer, in world space. */
|
|
511
|
+
readonly normal: Vector3;
|
|
512
|
+
/** For an edge or midpoint snap, the edge in world space. */
|
|
513
|
+
readonly edge: readonly [Vector3, Vector3] | null;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The edges a pointer can snap to: segment endpoints, six floats per segment,
|
|
517
|
+
* in the space `matrixWorld` maps to the world.
|
|
518
|
+
*
|
|
519
|
+
* In practice this is the part's own edge overlay — `regionEdgesGeometry` —
|
|
520
|
+
* which draws a line only where two analytic surfaces meet. Those are the edges
|
|
521
|
+
* a machinist would put a caliper on; the diagonal a tessellator drew across a
|
|
522
|
+
* flat face is not, and snapping to it would measure the mesh rather than the
|
|
523
|
+
* part.
|
|
524
|
+
*/
|
|
525
|
+
interface SnapEdges {
|
|
526
|
+
readonly positions: ArrayLike<number>;
|
|
527
|
+
readonly matrixWorld: Matrix4;
|
|
528
|
+
}
|
|
529
|
+
/** What the ray hit, before snapping. */
|
|
530
|
+
interface SnapHit {
|
|
531
|
+
readonly point: Vector3;
|
|
532
|
+
readonly normal: Vector3;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Where a hit on the part snaps to.
|
|
536
|
+
*
|
|
537
|
+
* Nearness is judged on screen, in pixels, because that is where the pointer
|
|
538
|
+
* is: a corner three pixels away is a corner however far the camera is. The
|
|
539
|
+
* search is bounded in world space first — a segment further from the hit than
|
|
540
|
+
* the widest reach could ever cover is skipped without being projected — and
|
|
541
|
+
* held to the surface the hit is on: an edge that is within reach of the point
|
|
542
|
+
* but well off its tangent plane is on the far side of a wall, and snapping to
|
|
543
|
+
* it would put a measurement on something the eye cannot see.
|
|
544
|
+
*/
|
|
545
|
+
declare function snapAt(hit: SnapHit, edges: SnapEdges | null, camera: ViewerCamera, viewport: ViewportSize): Snap;
|
|
546
|
+
/** Where a world point lands on the canvas, in CSS pixels from the top left. */
|
|
547
|
+
declare function screenPoint(camera: ViewerCamera, point: Vector3, viewport: ViewportSize, into?: Vector2): Vector2;
|
|
548
|
+
type MeasureMode = 'distance' | 'angle';
|
|
549
|
+
/** How many clicks each kind of measurement takes. */
|
|
550
|
+
declare const POINTS_PER_MEASUREMENT: Record<MeasureMode, number>;
|
|
551
|
+
interface DistanceMeasurement {
|
|
552
|
+
readonly id: number;
|
|
553
|
+
readonly kind: 'distance';
|
|
554
|
+
readonly points: readonly [Vec3, Vec3];
|
|
555
|
+
}
|
|
556
|
+
/** Three points: the two arms' ends, with the vertex between them. */
|
|
557
|
+
interface AngleMeasurement {
|
|
558
|
+
readonly id: number;
|
|
559
|
+
readonly kind: 'angle';
|
|
560
|
+
readonly points: readonly [Vec3, Vec3, Vec3];
|
|
561
|
+
}
|
|
562
|
+
type Measurement = DistanceMeasurement | AngleMeasurement;
|
|
563
|
+
/**
|
|
564
|
+
* A measurement from the points clicked so far, or `null` while there are not
|
|
565
|
+
* enough of them yet.
|
|
566
|
+
*/
|
|
567
|
+
declare function measurementFromPoints(kind: MeasureMode, points: readonly Vec3[], id: number): Measurement | null;
|
|
568
|
+
/** An id no measurement in `held` has. */
|
|
569
|
+
declare function nextMeasurementId(held: readonly Measurement[]): number;
|
|
570
|
+
declare function distanceBetween(a: Vec3, b: Vec3): number;
|
|
571
|
+
/** `b − a`, per axis. */
|
|
572
|
+
declare function deltaBetween(a: Vec3, b: Vec3): Vec3;
|
|
573
|
+
declare function midpoint(a: Vec3, b: Vec3): Vec3;
|
|
574
|
+
/**
|
|
575
|
+
* The angle at `vertex` between the arms to `a` and `b`, in degrees, from 0 to
|
|
576
|
+
* 180. An arm with no length has no direction, and the angle is 0.
|
|
577
|
+
*/
|
|
578
|
+
declare function angleAt(vertex: Vec3, a: Vec3, b: Vec3): number;
|
|
579
|
+
type Axis = 'x' | 'y' | 'z';
|
|
580
|
+
/** One leg of a distance's X, Y, Z breakdown. */
|
|
581
|
+
interface DeltaLeg {
|
|
582
|
+
readonly axis: Axis;
|
|
583
|
+
readonly from: Vec3;
|
|
584
|
+
readonly to: Vec3;
|
|
585
|
+
readonly length: number;
|
|
586
|
+
}
|
|
587
|
+
/** Where a point lands when it is held to one axis from `from`. */
|
|
588
|
+
interface AxisLock {
|
|
589
|
+
readonly axis: Axis;
|
|
590
|
+
readonly point: Vec3;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* `to`, held to the one axis it has moved furthest along from `from`.
|
|
594
|
+
*
|
|
595
|
+
* The ortho lock a CAD user reaches for with Shift: the next point is dropped
|
|
596
|
+
* onto the X, Y or Z line through the last one, so a distance meant to be "how
|
|
597
|
+
* long is this" is not "how long is this, and a little sideways". Which axis is
|
|
598
|
+
* whichever the pointer is furthest along, and a pointer that has not moved
|
|
599
|
+
* stays put on X.
|
|
600
|
+
*/
|
|
601
|
+
declare function lockToAxis(from: Vec3, to: Vec3): AxisLock;
|
|
602
|
+
/**
|
|
603
|
+
* A distance broken into its X, Y and Z parts, as a path from `a` to `b` that
|
|
604
|
+
* runs along one axis at a time.
|
|
605
|
+
*
|
|
606
|
+
* Only the legs with length are returned, and none at all when the distance
|
|
607
|
+
* already runs along an axis: a single leg would lie on top of the line it
|
|
608
|
+
* explains and say nothing the line does not.
|
|
609
|
+
*/
|
|
610
|
+
declare function deltaLegs(a: Vec3, b: Vec3): readonly DeltaLeg[];
|
|
611
|
+
/**
|
|
612
|
+
* Points along the arc an angle is drawn with, from the arm to `a` round to
|
|
613
|
+
* the arm to `b`, at `radius` from the vertex.
|
|
614
|
+
*
|
|
615
|
+
* Interpolated on the sphere rather than along the chord, so the arc is an
|
|
616
|
+
* arc. Two arms that point the same way, or opposite ways, have no plane to
|
|
617
|
+
* draw in, and get their two ends and nothing between.
|
|
618
|
+
*/
|
|
619
|
+
declare function angleArc(vertex: Vec3, a: Vec3, b: Vec3, radius: number, segments?: number): Vector3[];
|
|
620
|
+
/** The radius an angle's arc is drawn at: a fraction of the shorter arm. */
|
|
621
|
+
declare function angleArcRadius(vertex: Vec3, a: Vec3, b: Vec3): number;
|
|
622
|
+
/** A length in millimetres, as the tool writes it when told nothing else. */
|
|
623
|
+
declare function formatMillimetres(mm: number, digits?: number): string;
|
|
624
|
+
declare function formatDegrees(degrees: number, digits?: number): string;
|
|
625
|
+
/** What a measurement's label says. `format` writes a length; angles write themselves. */
|
|
626
|
+
declare function measurementLabel(measurement: Measurement, format?: (mm: number) => string): string;
|
|
627
|
+
/** Where a measurement's label sits: the middle of a distance, the vertex of an angle. */
|
|
628
|
+
declare function measurementLabelAnchor(measurement: Measurement): Vec3;
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* The class every label carries, for a stylesheet to find.
|
|
632
|
+
*
|
|
633
|
+
* The labels ship with no styling at all. Each one is a `<div>` with this
|
|
634
|
+
* class, `data-measure-label` naming what it is — `distance`, `angle`, `delta`
|
|
635
|
+
* for one leg of a distance's breakdown, or `live` for the readout that
|
|
636
|
+
* follows the pointer — `data-axis` on a leg, and `data-measurement-id` on
|
|
637
|
+
* anything belonging to a finished measurement. The README shows a stylesheet
|
|
638
|
+
* to start from.
|
|
639
|
+
*/
|
|
640
|
+
declare const MEASURE_LABEL_CLASS = "toolpath-measure-label";
|
|
641
|
+
/** What a label is, for a stylesheet to tell them apart. */
|
|
642
|
+
type MeasureLabelKind = 'distance' | 'angle' | 'delta' | 'live';
|
|
643
|
+
interface MeasureToolProps {
|
|
644
|
+
/**
|
|
645
|
+
* What a click sequence measures. `distance` takes two points; `angle` takes
|
|
646
|
+
* three — one arm's end, the vertex, the other arm's end. Changing it drops
|
|
647
|
+
* any points already placed.
|
|
648
|
+
*/
|
|
649
|
+
mode?: MeasureMode;
|
|
650
|
+
/**
|
|
651
|
+
* The measurements on screen. Pass it to own them; omit it and the tool keeps
|
|
652
|
+
* its own list, which `onChange` still reports.
|
|
653
|
+
*/
|
|
654
|
+
measurements?: readonly Measurement[];
|
|
655
|
+
/** The list changed: a measurement finished, or Delete removed the last one. */
|
|
656
|
+
onChange?: (measurements: readonly Measurement[]) => void;
|
|
657
|
+
/**
|
|
658
|
+
* Whether a distance also shows its X, Y and Z parts, as dashed legs in the
|
|
659
|
+
* axis colours with their own labels. On by default; a distance that already
|
|
660
|
+
* runs along an axis shows none either way.
|
|
661
|
+
*/
|
|
662
|
+
showDeltas?: boolean;
|
|
663
|
+
/**
|
|
664
|
+
* Writes a length. Millimetres to two places unless told otherwise — the part
|
|
665
|
+
* is in millimetres, and converting is the consumer's call.
|
|
666
|
+
*/
|
|
667
|
+
format?: (mm: number) => string;
|
|
668
|
+
/**
|
|
669
|
+
* Added to every label's `className`, beside {@link MEASURE_LABEL_CLASS} —
|
|
670
|
+
* a Tailwind utility list, or a CSS module's class.
|
|
671
|
+
*/
|
|
672
|
+
labelClassName?: string;
|
|
673
|
+
theme?: Partial<ViewerTheme>;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Measure the part from inside the viewport. Mount it beside `<PartMesh>`.
|
|
677
|
+
*
|
|
678
|
+
* The pointer snaps to what is under it — a corner first, then an edge's
|
|
679
|
+
* midpoint, then the edge, then the face — and a marker shows where the click
|
|
680
|
+
* will land. A distance is two clicks and an angle three; the line follows the
|
|
681
|
+
* pointer between them with a live readout. Finished measurements stay up,
|
|
682
|
+
* drawn over the part with a label each, until Delete removes the last one or
|
|
683
|
+
* the tool is unmounted. Escape drops the points of one in progress. Holding
|
|
684
|
+
* Shift holds the next point to the X, Y or Z line through the last one — the
|
|
685
|
+
* axis the pointer is furthest along — so a length along an edge is measured
|
|
686
|
+
* along the edge and not a little across it.
|
|
687
|
+
*
|
|
688
|
+
* Nothing needs wiring. While it is mounted the part reports no hovers or
|
|
689
|
+
* picks — a click that places a point is not a click that selects a face — and
|
|
690
|
+
* unmounting it hands the pointer back. It snaps to the edges the part draws,
|
|
691
|
+
* which are the boundaries between analytic surfaces rather than the mesh's
|
|
692
|
+
* triangles, so a corner is a corner of the part and not of a facet.
|
|
693
|
+
*
|
|
694
|
+
* Lines are drawn at a width in pixels, through drei's `Line`, because a
|
|
695
|
+
* WebGL line is one pixel wide whatever is asked of it and one pixel of blue
|
|
696
|
+
* over a white face is not a measurement anybody can see.
|
|
697
|
+
*
|
|
698
|
+
* Labels are DOM elements laid over the canvas, so they are text a test can
|
|
699
|
+
* read — and they are **unstyled**. This is a headless component: what a
|
|
700
|
+
* label looks like is the consumer's stylesheet's, through
|
|
701
|
+
* {@link MEASURE_LABEL_CLASS} and the data attributes on each one. The one
|
|
702
|
+
* thing set here is that the element each label is wrapped in takes no
|
|
703
|
+
* pointer events, because a label over the part that took the click meant
|
|
704
|
+
* for the face under it would be a bug rather than a look.
|
|
705
|
+
*/
|
|
706
|
+
declare const MeasureTool: ({ mode, measurements, onChange, showDeltas, format, labelClassName, theme, }: MeasureToolProps) => react.JSX.Element | null;
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* The cut a viewer holds for itself, when nobody else is holding it.
|
|
710
|
+
*
|
|
711
|
+
* `<PartMesh section>` is the controlled path and is untouched by this. The
|
|
712
|
+
* store exists for the other one: `<SectionTool>` mounted beside the part with
|
|
713
|
+
* no wiring at all. The two are siblings and cannot see each other, so the
|
|
714
|
+
* viewer keeps one of these and each reads or writes it — the tool sets the
|
|
715
|
+
* cut, the part clips to it and moves it when the handle is dragged.
|
|
716
|
+
*
|
|
717
|
+
* It also carries whether a tool is **engaged** — mounted at all. While one
|
|
718
|
+
* is, the pointer over the part belongs to it: the part reports no hovers or
|
|
719
|
+
* picks, so clicking a face to cut through it does not also select it, the
|
|
720
|
+
* hover paint does not sit under the tool's own preview, and a cut part is
|
|
721
|
+
* read as a cut rather than picked at. Unmounting the tool hands the pointer
|
|
722
|
+
* back. `<MeasureTool>` engages it too — a click that puts a measurement point
|
|
723
|
+
* on a face is not a request to select the face — which is why the flag is a
|
|
724
|
+
* count rather than a boolean: two tools up at once let go one at a time.
|
|
725
|
+
*
|
|
726
|
+
* A subscription rather than React state, so a drag re-renders the two
|
|
727
|
+
* subscribers and not everything under the canvas.
|
|
728
|
+
*/
|
|
729
|
+
interface SectionStore {
|
|
730
|
+
get(): SectionOptions | null;
|
|
731
|
+
set(next: SectionOptions | null): void;
|
|
732
|
+
/** Whether a tool is mounted, and the part should stay out of the way. */
|
|
733
|
+
isEngaged(): boolean;
|
|
734
|
+
/** Counted: a tool engages on mount and disengages on unmount, and the part waits for the last. */
|
|
735
|
+
setEngaged(engaged: boolean): void;
|
|
736
|
+
/** Notified on every change to either the cut or the engaged flag. */
|
|
737
|
+
subscribe(listener: () => void): () => void;
|
|
738
|
+
}
|
|
739
|
+
declare function createSectionStore(): SectionStore;
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* A cut part, re-sampled for measuring.
|
|
743
|
+
*
|
|
744
|
+
* A clipping plane hides half of a part without changing a triangle, so the
|
|
745
|
+
* mesh and its edges still describe the whole thing — and a tool that snaps
|
|
746
|
+
* to them would offer corners nobody can see, and nothing at all on the cut.
|
|
747
|
+
* This is what the measure tool works against instead: the part's edges
|
|
748
|
+
* trimmed to the half that is left, the outline where the plane passes
|
|
749
|
+
* through solid material, and a test for whether a point on the plane is on
|
|
750
|
+
* the capped face. It is rebuilt whenever the plane moves; see
|
|
751
|
+
* `measure-tool.tsx`.
|
|
752
|
+
*
|
|
753
|
+
* Everything here is in one space — the mesh's own — and the plane is
|
|
754
|
+
* expected in it too. Six floats per segment throughout, the layout
|
|
755
|
+
* `regionEdgesGeometry` uses.
|
|
756
|
+
*/
|
|
757
|
+
interface SectionSample {
|
|
758
|
+
/** The edges of the part on the kept side, plus the cut's outline. */
|
|
759
|
+
readonly edges: Float32Array;
|
|
760
|
+
/** The cut's outline alone: where the plane crosses solid material. */
|
|
761
|
+
readonly contour: Float32Array;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Trims segments to the half-space a plane keeps.
|
|
765
|
+
*
|
|
766
|
+
* A segment wholly on the removed side goes; one that crosses is cut at the
|
|
767
|
+
* plane and its kept part stays. `Plane` keeps the side its normal points
|
|
768
|
+
* into — where the signed distance is positive — as the part's own clipping
|
|
769
|
+
* does.
|
|
770
|
+
*/
|
|
771
|
+
declare function clipSegments(positions: ArrayLike<number>, plane: Plane): Float32Array;
|
|
772
|
+
/**
|
|
773
|
+
* Where a plane passes through a mesh: one segment per triangle it crosses,
|
|
774
|
+
* then straight runs inside one region stitched into one segment each.
|
|
775
|
+
*
|
|
776
|
+
* The stitching is what makes the outline snappable. A flat face is two
|
|
777
|
+
* triangles or two hundred, and the plane crosses each of them separately,
|
|
778
|
+
* so before stitching every triangle boundary on the face was a corner the
|
|
779
|
+
* pointer would snap to. After it, a corner is where the outline turns —
|
|
780
|
+
* where the plane leaves one surface for the next — and on a curved surface
|
|
781
|
+
* the facets stay, as they do in the part's own edges.
|
|
782
|
+
*
|
|
783
|
+
* `regionOf` says which surface each triangle belongs to, indexed by
|
|
784
|
+
* triangle; without it every triangle is one surface, which stitches a
|
|
785
|
+
* straight run across a fold only if the fold is perfectly flat, which it is
|
|
786
|
+
* not.
|
|
787
|
+
*/
|
|
788
|
+
declare function sectionContour(positions: ArrayLike<number>, regionOf: ArrayLike<number> | null, plane: Plane): Float32Array;
|
|
789
|
+
/**
|
|
790
|
+
* Whether a point on the plane lies inside the cut's outline — on the capped
|
|
791
|
+
* face rather than in the air beside it.
|
|
792
|
+
*
|
|
793
|
+
* Even-odd against every outline segment, in the plane's own two axes, so a
|
|
794
|
+
* part cut through a bore is inside the outer loop and outside the hole's.
|
|
795
|
+
*/
|
|
796
|
+
declare function pointInContour(point: Vector3, plane: Plane, contour: ArrayLike<number>): boolean;
|
|
797
|
+
/**
|
|
798
|
+
* Where a ray meets the capped face, or `null` when it misses the cap — a
|
|
799
|
+
* ray through the open half that lands beside the part, or one that reaches
|
|
800
|
+
* the plane from behind.
|
|
801
|
+
*/
|
|
802
|
+
declare function capHit(ray: Ray, plane: Plane, contour: ArrayLike<number>): Vector3 | null;
|
|
803
|
+
/** The edges and outline of a part cut by `plane`, everything in the mesh's own space. */
|
|
804
|
+
declare function sampleSection(meshPositions: ArrayLike<number>, regionOf: ArrayLike<number> | null, edgePositions: ArrayLike<number> | null, plane: Plane): SectionSample;
|
|
805
|
+
|
|
806
|
+
type CapUniforms = {
|
|
807
|
+
uMask: {
|
|
808
|
+
value: Texture | null;
|
|
809
|
+
};
|
|
810
|
+
uResolution: {
|
|
811
|
+
value: Vector2;
|
|
812
|
+
};
|
|
813
|
+
uFill: {
|
|
814
|
+
value: Color;
|
|
815
|
+
};
|
|
816
|
+
uHatch: {
|
|
817
|
+
value: Color;
|
|
818
|
+
};
|
|
819
|
+
uOutline: {
|
|
820
|
+
value: Color;
|
|
821
|
+
};
|
|
822
|
+
uPitch: {
|
|
823
|
+
value: number;
|
|
824
|
+
};
|
|
825
|
+
uLine: {
|
|
826
|
+
value: number;
|
|
827
|
+
};
|
|
828
|
+
uThickness: {
|
|
829
|
+
value: number;
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
type CapMaterial = ShaderMaterial & {
|
|
833
|
+
uniforms: CapUniforms;
|
|
834
|
+
};
|
|
835
|
+
/**
|
|
836
|
+
* The render target the mask is drawn into. Sized later, to the drawing
|
|
837
|
+
* buffer, because that is what `gl_FragCoord` is measured in and a mask at
|
|
838
|
+
* any other size would put the outline beside the edge rather than on it.
|
|
839
|
+
*/
|
|
840
|
+
declare function createMaskTarget(): WebGLRenderTarget;
|
|
841
|
+
/**
|
|
842
|
+
* The cap's own material, filled only where the main pass's stencil says the
|
|
843
|
+
* plane is inside material. Unthemed until {@link applyCapTheme} is called.
|
|
844
|
+
*/
|
|
845
|
+
declare function createCapMaterial(mask: Texture): CapMaterial;
|
|
846
|
+
/** Re-themes a cap in place, and re-scales its hatch for the device pixel ratio. */
|
|
847
|
+
declare function applyCapTheme(material: CapMaterial, theme: ViewerTheme, dpr: number): void;
|
|
848
|
+
/**
|
|
849
|
+
* The two materials of the stencil pass: back faces count up, front faces
|
|
850
|
+
* count down, and what is left non-zero is where the plane is inside solid
|
|
851
|
+
* material. Nothing is written but the stencil.
|
|
852
|
+
*/
|
|
853
|
+
declare function createStencilMaterials(clip: Plane[]): [MeshBasicMaterial, MeshBasicMaterial];
|
|
854
|
+
/** The quad the mask pass draws where the stencil is set — white on black. */
|
|
855
|
+
declare function createMaskMaterial(): MeshBasicMaterial;
|
|
856
|
+
|
|
195
857
|
interface ArrowPlacement {
|
|
196
858
|
/** Where the arrow's tip sits: outside the box, on the direction's ray. */
|
|
197
859
|
readonly tip: Vector3;
|
|
@@ -398,200 +1060,6 @@ type SurfaceOf = ReadonlyMap<number, number>;
|
|
|
398
1060
|
*/
|
|
399
1061
|
declare function visualSurfaces(regions: readonly PartModelRegion[]): SurfaceOf;
|
|
400
1062
|
|
|
401
|
-
type Projection = 'orthographic' | 'perspective';
|
|
402
|
-
type ViewerCamera = OrthographicCamera | PerspectiveCamera;
|
|
403
|
-
interface ViewportSize {
|
|
404
|
-
readonly width: number;
|
|
405
|
-
readonly height: number;
|
|
406
|
-
}
|
|
407
|
-
/** Vertical field of view, in degrees, for the perspective camera. */
|
|
408
|
-
declare const PERSPECTIVE_FOV = 30;
|
|
409
|
-
/** Padding around the framed bounds, as a multiple of its radius. */
|
|
410
|
-
declare const DEFAULT_FIT_MARGIN = 1.2;
|
|
411
|
-
/** Marks scene furniture — grid, axes — that the camera should not frame. */
|
|
412
|
-
declare const EXCLUDE_FROM_FRAME = "viewerExcludeFromFrame";
|
|
413
|
-
/**
|
|
414
|
-
* What the camera frames: a bounding *sphere*, not a box.
|
|
415
|
-
*
|
|
416
|
-
* A sphere makes framing rotation-invariant — the part stays fully visible from
|
|
417
|
-
* every angle, and a resize never needs to know the current pose. Framing
|
|
418
|
-
* per-axis box dimensions instead means recomputing the camera distance on
|
|
419
|
-
* every resize, and still clipping on some orientations.
|
|
420
|
-
*/
|
|
421
|
-
interface SceneBounds {
|
|
422
|
-
readonly center: Vector3;
|
|
423
|
-
readonly radius: number;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Bounds for a scene with nothing in it. A viewer is mounted before it has a
|
|
427
|
-
* part, and a camera with a zero-size frustum renders nothing at all.
|
|
428
|
-
*/
|
|
429
|
-
declare function defaultBounds(): SceneBounds;
|
|
430
|
-
/**
|
|
431
|
-
* A container can be laid out at zero size, and a degenerate aspect ratio
|
|
432
|
-
* produces a `NaN` projection matrix that never recovers. Fall back to square.
|
|
433
|
-
*/
|
|
434
|
-
declare function aspectRatio(size: ViewportSize): number;
|
|
435
|
-
/**
|
|
436
|
-
* A world length that covers `pixels` on screen at `point`.
|
|
437
|
-
*
|
|
438
|
-
* Anything sized in world units is a thumbnail on a plate and a wall on an
|
|
439
|
-
* insert; the whole reason a control is measured this way is that it is a
|
|
440
|
-
* control rather than part of the model. The section handle and the orbit
|
|
441
|
-
* target marker both size themselves with it, which is why it lives here with
|
|
442
|
-
* the camera rather than beside either of them.
|
|
443
|
-
*
|
|
444
|
-
* The two projections answer differently on purpose. An orthographic frustum
|
|
445
|
-
* covers the same world height wherever the point is, so distance does not
|
|
446
|
-
* enter; a perspective one covers more the further away it looks, so it does.
|
|
447
|
-
*/
|
|
448
|
-
declare function screenLength(camera: ViewerCamera, point: Vector3, viewport: ViewportSize, pixels: number): number;
|
|
449
|
-
declare function boundsFromBox(box: Box3): SceneBounds;
|
|
450
|
-
/**
|
|
451
|
-
* The bounds of everything worth framing under `root`.
|
|
452
|
-
*
|
|
453
|
-
* Skips objects flagged with {@link EXCLUDE_FROM_FRAME} and their descendants:
|
|
454
|
-
* a 100 mm grid around a 6 mm part would otherwise frame the grid, and the part
|
|
455
|
-
* would be a speck.
|
|
456
|
-
*/
|
|
457
|
-
declare function contentBounds(root: Object3D, into: Box3): SceneBounds;
|
|
458
|
-
/**
|
|
459
|
-
* The distance at which a sphere of `radius` fits inside a perspective frustum.
|
|
460
|
-
* Uses the narrower of the two field-of-view angles, so a portrait viewport
|
|
461
|
-
* frames on width and a landscape one on height.
|
|
462
|
-
*/
|
|
463
|
-
declare function perspectiveFitDistance(fovDegrees: number, aspect: number, radius: number): number;
|
|
464
|
-
/**
|
|
465
|
-
* Half-height of an orthographic frustum that fits a sphere of `radius`. A
|
|
466
|
-
* portrait viewport grows the height so the width still clears the sphere.
|
|
467
|
-
*/
|
|
468
|
-
declare function orthographicHalfHeight(aspect: number, radius: number): number;
|
|
469
|
-
declare function fitDistance(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): number;
|
|
470
|
-
declare function startPosition(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): Vector3;
|
|
471
|
-
/**
|
|
472
|
-
* How far out the wheel may pull, as a fraction of the fitted framing: the part
|
|
473
|
-
* may shrink to a quarter of the view and no further. Past that it is a speck
|
|
474
|
-
* with no cue that Fit is the way back.
|
|
475
|
-
*/
|
|
476
|
-
declare const MIN_FRAME_RATIO = 0.25;
|
|
477
|
-
/**
|
|
478
|
-
* How far in the wheel may push, as a multiple of the fitted framing.
|
|
479
|
-
*
|
|
480
|
-
* Both ratios are the legacy viewer's `minZoom` 0.25 / `maxZoom` 10
|
|
481
|
-
* (`three-object.tsx:289-311`), which transfer almost exactly: legacy's
|
|
482
|
-
* orthographic frustum was `largestDimension * 2` tall and this one is the
|
|
483
|
-
* fitted bounding sphere, which for a cube agree within a few percent. So the
|
|
484
|
-
* numbers are the ones that shipped, and here they carry a meaning they did not
|
|
485
|
-
* have there — `zoom` 1 *is* the fitted framing, so 0.25 and 10 say exactly
|
|
486
|
-
* what a reader thinks they say.
|
|
487
|
-
*/
|
|
488
|
-
declare const MAX_FRAME_RATIO = 10;
|
|
489
|
-
/** How far the camera may travel, in the two units the two cameras travel in. */
|
|
490
|
-
interface CameraLimits {
|
|
491
|
-
readonly minZoom: number;
|
|
492
|
-
readonly maxZoom: number;
|
|
493
|
-
readonly minDistance: number;
|
|
494
|
-
readonly maxDistance: number;
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* The clamps for a scene, so the wheel cannot leave the viewport empty.
|
|
498
|
-
*
|
|
499
|
-
* Without them it can, from either end, and both cameras can do it: an
|
|
500
|
-
* orthographic `camera.zoom` reaches 1e30 in sixty notches, and a perspective
|
|
501
|
-
* camera dives inside the part in eight, because `minDistance` defaults to
|
|
502
|
-
* `Number.EPSILON`. Fit recovers from both — but Fit is a double middle click
|
|
503
|
-
* and nothing on screen says so, which makes it a way out rather than an
|
|
504
|
-
* answer.
|
|
505
|
-
*
|
|
506
|
-
* One rule for both cameras: the wheel may take the part from a quarter of its
|
|
507
|
-
* fitted size to ten times it. Under an orthographic camera that scale *is* the
|
|
508
|
-
* frustum, so the ratios land on `zoom`; under a perspective camera apparent
|
|
509
|
-
* size is the inverse of distance, so they land on `distance` — which is why
|
|
510
|
-
* they swap sides between the two.
|
|
511
|
-
*
|
|
512
|
-
* Pure, and derived from `bounds`, so it re-derives wherever the scene is
|
|
513
|
-
* re-measured rather than being sampled once at mount.
|
|
514
|
-
*
|
|
515
|
-
* `framed` is what the view has been framed *on*, when that is not the whole
|
|
516
|
-
* scene — `frameBox` on a single feature. The band is then taken about that
|
|
517
|
-
* framing as well as about the scene's, because a band centred on the part is
|
|
518
|
-
* the wrong band for a view of a 3 mm hole in it: ten times the *part's* fitted
|
|
519
|
-
* size can be less than once the hole's, so the framing the caller asked for sat
|
|
520
|
-
* outside the clamps meant to keep it in view. Under an orthographic camera
|
|
521
|
-
* `zoom` is measured against a frustum still sized to the scene, so the framing
|
|
522
|
-
* enters as the zoom ratio it takes to reach; under a perspective one it enters
|
|
523
|
-
* as its own fitted distance.
|
|
524
|
-
*
|
|
525
|
-
* Widened to the union of the two rather than moved onto `framed`, and that is
|
|
526
|
-
* the whole of why this takes both. A band that simply followed the last framing
|
|
527
|
-
* would take the part away: framing a hole would put "far enough out to see what
|
|
528
|
-
* the hole is in" past `maxDistance`, so the wheel could no longer get back to
|
|
529
|
-
* the part it had just been looking at, and Fit — a gesture with nothing on
|
|
530
|
-
* screen to say so — would be the only way. Reaching further in must not cost
|
|
531
|
-
* the reach back out, so each end takes whichever bound is looser. With no
|
|
532
|
-
* `framed`, or one the size of the scene, both ends collapse to the scene's own
|
|
533
|
-
* and this is the function it was before.
|
|
534
|
-
*/
|
|
535
|
-
declare function cameraLimits(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number, framed?: SceneBounds): CameraLimits;
|
|
536
|
-
/**
|
|
537
|
-
* The box the orbit target is confined to — the fitted framing, several frames
|
|
538
|
-
* over. Written into `into` so the per-frame path allocates nothing.
|
|
539
|
-
*/
|
|
540
|
-
declare function targetBoundary(bounds: SceneBounds, into: Box3, margin?: number): Box3;
|
|
541
|
-
/**
|
|
542
|
-
* Points an existing camera's frustum at `bounds` for the current viewport.
|
|
543
|
-
* Pose is untouched — that belongs to the controls.
|
|
544
|
-
*/
|
|
545
|
-
declare function applyProjection(camera: ViewerCamera, size: ViewportSize, bounds: SceneBounds, margin?: number): void;
|
|
546
|
-
/**
|
|
547
|
-
* The world-up convention. The part data is Z-up (millimetres, no conversion) —
|
|
548
|
-
* not the glTF-conventional Y-up — so the camera has to say so explicitly.
|
|
549
|
-
*/
|
|
550
|
-
declare const CAD_CAMERA_UP: Vector3;
|
|
551
|
-
/**
|
|
552
|
-
* `up` re-squared against a view direction: the part of it perpendicular to
|
|
553
|
-
* `view`, written into `into`.
|
|
554
|
-
*
|
|
555
|
-
* This is what keeps the horizon level while a free orbit carries the camera
|
|
556
|
-
* over a pole — the up vector follows the view instead of flipping. The
|
|
557
|
-
* controls run it on every update, which is what makes the next sentence the
|
|
558
|
-
* important one.
|
|
559
|
-
*
|
|
560
|
-
* **It is path-dependent.** It is a projection, so it discards the component it
|
|
561
|
-
* removes and cannot put it back: running it at one view and then another does
|
|
562
|
-
* not give what running it once at the second view would. So a camera carries
|
|
563
|
-
* the roll of every pose it has passed through, and any pose that is meant to
|
|
564
|
-
* be canonical — the opening frame, a reset, a named view — has to say what
|
|
565
|
-
* `up` is rather than inherit it. `viewer.tsx` does, at `resetContent`. The
|
|
566
|
-
* test beside this pins both halves.
|
|
567
|
-
*
|
|
568
|
-
* `into` may be `view` or `up` — three.js `crossVectors` reads all six
|
|
569
|
-
* components before it writes any, and the intermediate is this module's own
|
|
570
|
-
* scratch, so writing the result over an input is safe. Said here because this
|
|
571
|
-
* is exported and an out-parameter invites the question; the test beside this
|
|
572
|
-
* pins it, so a three.js change cannot quietly turn the answer around.
|
|
573
|
-
*
|
|
574
|
-
* `view` and `up` being parallel is the one degenerate case: their cross is
|
|
575
|
-
* zero, `normalize` leaves it zero, and the result is a zero vector rather than
|
|
576
|
-
* an error. A caller with a free-orbiting camera reaches that at the pole,
|
|
577
|
-
* which is what `freeOrbit` and a squared `up` at every canonical pose exist to
|
|
578
|
-
* keep it away from.
|
|
579
|
-
*/
|
|
580
|
-
declare function adaptedUp(view: Vector3, up: Vector3, into: Vector3): Vector3;
|
|
581
|
-
/** Named viewing directions, as unit vectors from the part toward the camera. */
|
|
582
|
-
declare const cadViewDirections: {
|
|
583
|
-
readonly front: Vector3;
|
|
584
|
-
readonly back: Vector3;
|
|
585
|
-
readonly left: Vector3;
|
|
586
|
-
readonly right: Vector3;
|
|
587
|
-
readonly top: Vector3;
|
|
588
|
-
readonly bottom: Vector3;
|
|
589
|
-
readonly isometric: Vector3;
|
|
590
|
-
};
|
|
591
|
-
type ViewerView = keyof typeof cadViewDirections;
|
|
592
|
-
/** The current orbit direction, so Fit can retain the direction being looked from. */
|
|
593
|
-
declare function currentViewDirection(camera: ViewerCamera, target: Vector3, into: Vector3): Vector3;
|
|
594
|
-
|
|
595
1063
|
/**
|
|
596
1064
|
* Mouse and trackpad presets.
|
|
597
1065
|
*
|
|
@@ -717,12 +1185,26 @@ interface ViewerControls {
|
|
|
717
1185
|
* see a wall and then asks to zoom to it wants it closer, not re-oriented.
|
|
718
1186
|
*/
|
|
719
1187
|
frameBox(box: Box3): void;
|
|
1188
|
+
/**
|
|
1189
|
+
* Sets or clears the cut the viewer holds for itself — the one `<SectionTool>`
|
|
1190
|
+
* places and `<PartMesh>` follows when it is given no `section` of its own.
|
|
1191
|
+
*
|
|
1192
|
+
* `null` clears it. A `<PartMesh section>` is not affected: that cut is the
|
|
1193
|
+
* consumer's, and this one is the viewer's.
|
|
1194
|
+
*/
|
|
1195
|
+
setSection(options: SectionOptions | null): void;
|
|
720
1196
|
}
|
|
721
1197
|
interface ViewerHandle extends ViewerControls {
|
|
722
1198
|
}
|
|
723
1199
|
type VectorLike = Pick<Vector3, 'x' | 'y' | 'z'>;
|
|
724
1200
|
|
|
725
1201
|
declare const useViewerControls: () => ViewerControls;
|
|
1202
|
+
/**
|
|
1203
|
+
* The cut this viewer holds for itself. See `render/section-store.ts` for why
|
|
1204
|
+
* it is a store: `<SectionTool>` writes it and `<PartMesh>` reads it, and the
|
|
1205
|
+
* two are siblings.
|
|
1206
|
+
*/
|
|
1207
|
+
declare const useSectionStore: () => SectionStore;
|
|
726
1208
|
/** Re-aims the orbit, or `null` when this viewer has the gesture turned off. */
|
|
727
1209
|
type Retarget = ((point: VectorLike) => void) | null;
|
|
728
1210
|
/**
|
|
@@ -1093,4 +1575,4 @@ declare const ORBIT_TARGET_FADE_MS = 1000;
|
|
|
1093
1575
|
*/
|
|
1094
1576
|
declare function orbitTargetOpacity(sinceHold: number, fade?: number): number;
|
|
1095
1577
|
|
|
1096
|
-
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 };
|
|
1578
|
+
export { ANGLE_ARC_FRACTION, ANGLE_ARC_SEGMENTS, AXIS_COLORS, type AngleMeasurement, type ArrowPlacement, Axes, type AxesProps, type Axis, type AxisLock, CAD_CAMERA_UP, CHAMFER, CadCameraControls, type CadCameraControlsProps, type CameraLimits, type CapMaterial, type CapUniforms, type ControlScheme, type CubeZone, DEFAULT_FIT_MARGIN, DELTA_DASH_PIXELS, DOUBLE_TAP_MS, type DeltaLeg, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, type DistanceMeasurement, type DoubleTapPoint, type DoubleTapTracker, EXCLUDE_FROM_FRAME, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, MAX_FRAME_RATIO, MEASURE_LABEL_CLASS, MEASURE_LINE_PIXELS, MEASURE_MARKER_PIXELS, MEASURE_RENDER_ORDER, MIN_FRAME_RATIO, type MeasureLabelKind, type MeasureMode, MeasureTool, type MeasureToolProps, type Measurement, 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, POINTS_PER_MEASUREMENT, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, type Projection, type RankingContext, RegionIndex, type Retarget, type RetargetPose, SNAP_MARKER_PIXELS, SNAP_PIXELS, type SceneBounds, SectionOptions, type SectionSample, SectionState, type SectionStore, SectionTool, type SectionToolProps, SectionView, type Snap, type SnapEdges, type SnapHit, type SnapKind, 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, angleArc, angleArcRadius, angleAt, applyCapTheme, applyProjection, arrowPlacement, aspectRatio, bestOwner, boundsFromBox, buildRegionIndex, cadViewDirections, cameraLimits, capHit, clipSegments, contentBounds, createCapMaterial, createMaskMaterial, createMaskTarget, createSectionStore, createStencilMaterials, cubeOutlineGeometry, cubeZones, currentViewDirection, cycleOwner, defaultBounds, deltaBetween, deltaLegs, directionIndexOf, directionLabel, distanceBetween, excludedFromFrame, featureTypeRank, fitDistance, formatDegrees, formatMillimetres, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, lockToAxis, measurementFromPoints, measurementLabel, measurementLabelAnchor, midpoint, movedFar, nextMeasurementId, orbitTargetOpacity, orthographicHalfHeight, panelGeometry, perspectiveFitDistance, pointInContour, rankOwners, regionAdjacency, regionEdgesGeometry, regionNormals, resolveSectionPlane, retargetPose, sameDirection, sampleSection, screenLength, screenPoint, sectionContour, snapAt, squaredUp, startPosition, targetBoundary, trackDoubleTaps, trackTaps, useContentBox, useRetarget, useSectionStore, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
|