@toolpath/viewer 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +866 -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 +620 -191
- package/dist/index.js +1015 -13
- 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, 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';
|
|
@@ -114,7 +114,7 @@ interface GridProps {
|
|
|
114
114
|
* fixed cells is either invisible on a 900 mm plate or a solid wash under a
|
|
115
115
|
* 12 mm insert.
|
|
116
116
|
*/
|
|
117
|
-
declare const Grid: ({ step, extent, color, opacity }: GridProps) => react.JSX.Element;
|
|
117
|
+
declare const Grid: ({ step, extent, color, opacity }: GridProps) => react.JSX.Element | null;
|
|
118
118
|
interface AxesProps {
|
|
119
119
|
size?: number;
|
|
120
120
|
}
|
|
@@ -192,199 +192,62 @@ 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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
|
199
207
|
}
|
|
200
208
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* The
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
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.
|
|
209
227
|
*/
|
|
210
|
-
declare
|
|
228
|
+
declare const SectionView: ({ geometry, box, plane, theme, showHandle, onDrag, }: SectionViewProps) => react.JSX.Element;
|
|
211
229
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
*
|
|
215
|
-
* Measured on a frame rather than in an effect, because a Suspense-loaded mesh
|
|
216
|
-
* does not exist yet when the effects around it run — the same reason the
|
|
217
|
-
* viewer's opening frame waits. Measured once: an overlay that re-fitted itself
|
|
218
|
-
* while the part was being orbited would be a grid that breathes.
|
|
219
|
-
*
|
|
220
|
-
* Scene furniture is excluded, so the grid and the axes do not size each other.
|
|
221
|
-
*/
|
|
222
|
-
declare function useContentBox(): Box3;
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Telling a click apart from the end of a drag.
|
|
226
|
-
*
|
|
227
|
-
* The browser calls both a click: press, move, release over the same element
|
|
228
|
-
* fires `click` however far the pointer travelled in between. In a viewport
|
|
229
|
-
* that is the difference between "select this face" and "I have finished
|
|
230
|
-
* orbiting" — and the part is one mesh, so releasing over a different face is
|
|
231
|
-
* still the same element and still a click.
|
|
232
|
-
*
|
|
233
|
-
* Left unguarded, every orbit that happens to end over the part selects
|
|
234
|
-
* whatever it ended on, throwing away the selection the orbit was made to look
|
|
235
|
-
* at.
|
|
236
|
-
*/
|
|
237
|
-
/** How far a pointer may travel and still be a click, in CSS pixels. */
|
|
238
|
-
declare const TAP_SLOP = 4;
|
|
239
|
-
interface TapPoint {
|
|
240
|
-
readonly clientX: number;
|
|
241
|
-
readonly clientY: number;
|
|
242
|
-
}
|
|
243
|
-
/** Whether the pointer travelled far enough for this to be a drag. */
|
|
244
|
-
declare function movedFar(from: TapPoint, to: TapPoint, slop?: number): boolean;
|
|
245
|
-
interface TapTracker {
|
|
246
|
-
/** Whether the gesture ending at this event was a click rather than a drag. */
|
|
247
|
-
isTap(event: TapPoint): boolean;
|
|
248
|
-
dispose(): void;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Watches an element for the start of every gesture, so its end can be judged.
|
|
252
|
-
*
|
|
253
|
-
* Listens in the capture phase: whatever else handles the press — the camera
|
|
254
|
-
* controls, a drag handle — the start of the gesture still has to be recorded,
|
|
255
|
-
* and a listener that runs after `stopPropagation` runs never.
|
|
256
|
-
*/
|
|
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;
|
|
304
|
-
|
|
305
|
-
/** Whether the gesture ending at this event was a click rather than a drag. */
|
|
306
|
-
type TapGuard = (event: TapPoint) => boolean;
|
|
307
|
-
/**
|
|
308
|
-
* A click that is a click, for anything inside the canvas.
|
|
309
|
-
*
|
|
310
|
-
* Bound to the canvas element rather than to the object, because the gesture
|
|
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.
|
|
317
|
-
*/
|
|
318
|
-
declare function useTapGuard(): TapGuard;
|
|
319
|
-
|
|
320
|
-
interface GridSpec {
|
|
321
|
-
/** Cell size in part units (millimetres). */
|
|
322
|
-
readonly step: number;
|
|
323
|
-
/** Half-width of the grid, so it spans `2 × extent`. */
|
|
324
|
-
readonly extent: number;
|
|
325
|
-
/** The plane the grid sits on: the bottom of the part, snapped to a step. */
|
|
326
|
-
readonly z: number;
|
|
327
|
-
readonly center: Vector3;
|
|
230
|
+
interface SectionToolProps {
|
|
231
|
+
theme?: Partial<ViewerTheme>;
|
|
328
232
|
}
|
|
329
233
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* world and has to be rotated into place — the Engine is Z-up, and a rotated
|
|
346
|
-
* helper is a thing to remember rather than a thing that is true.
|
|
347
|
-
*/
|
|
348
|
-
declare function gridGeometry(spec: GridSpec): BufferGeometry;
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* The lines between regions, and nothing else.
|
|
352
|
-
*
|
|
353
|
-
* `EdgesGeometry` draws an edge wherever two facets meet at more than some
|
|
354
|
-
* angle, which is a guess standing in for "is this a real edge". On a machined
|
|
355
|
-
* part the guess is wrong in both directions: a small bore tessellated into
|
|
356
|
-
* twelve facets has 30° between them and gets drawn as a nut, while a shallow
|
|
357
|
-
* chamfer meeting a wall at 12° gets no line at all.
|
|
358
|
-
*
|
|
359
|
-
* The report already knows. A region is one analytic surface, so an edge inside
|
|
360
|
-
* one is tessellation and an edge between two is a real boundary — the same
|
|
361
|
-
* fact that makes region-aware shading possible, used for the other half of
|
|
362
|
-
* what makes a part read as a part.
|
|
363
|
-
*
|
|
364
|
-
* With one qualification. The Engine splits a surface where that makes a better
|
|
365
|
-
* machining plan, and those splits are boundaries between regions without being
|
|
366
|
-
* edges of the part: a floor cut in two to be reached from two directions is
|
|
367
|
-
* still one face to look at. So the walk is over *visual surfaces* — regions
|
|
368
|
-
* grouped by the kernel's exact `splitOrigin` lineage — and a split leaves no
|
|
369
|
-
* line. Nothing about picking or features goes through that grouping.
|
|
370
|
-
*
|
|
371
|
-
* The mesh must be non-indexed, which `parsePartGeometry` guarantees.
|
|
372
|
-
*/
|
|
373
|
-
declare function regionEdgesGeometry(geometry: BufferGeometry, model: Pick<PartModel, 'regions' | 'regionIndex'>): BufferGeometry;
|
|
374
|
-
|
|
375
|
-
/** Region index → the visual surface it belongs to. */
|
|
376
|
-
type SurfaceOf = ReadonlyMap<number, number>;
|
|
377
|
-
/**
|
|
378
|
-
* The visual surface of each post-split region.
|
|
379
|
-
*
|
|
380
|
-
* The kernel divides B-rep faces to make machining relationships unambiguous.
|
|
381
|
-
* Those divisions remain meaningful to feature extraction and picking, but
|
|
382
|
-
* they are not physical edges. `splitOrigin` is the kernel's exact lineage
|
|
383
|
-
* relation: equal values mean the regions were one face before that analysis
|
|
384
|
-
* split. It is therefore the single grouping used for rendering, rather than
|
|
385
|
-
* attempting to infer continuations from mesh normals or surface kinds.
|
|
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.
|
|
386
249
|
*/
|
|
387
|
-
declare
|
|
250
|
+
declare const SectionTool: ({ theme }: SectionToolProps) => react.JSX.Element | null;
|
|
388
251
|
|
|
389
252
|
type Projection = 'orthographic' | 'perspective';
|
|
390
253
|
type ViewerCamera = OrthographicCamera | PerspectiveCamera;
|
|
@@ -443,6 +306,13 @@ declare function boundsFromBox(box: Box3): SceneBounds;
|
|
|
443
306
|
* would be a speck.
|
|
444
307
|
*/
|
|
445
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;
|
|
446
316
|
/**
|
|
447
317
|
* The distance at which a sphere of `radius` fits inside a perspective frustum.
|
|
448
318
|
* Uses the narrower of the two field-of-view angles, so a portrait viewport
|
|
@@ -580,6 +450,551 @@ type ViewerView = keyof typeof cadViewDirections;
|
|
|
580
450
|
/** The current orbit direction, so Fit can retain the direction being looked from. */
|
|
581
451
|
declare function currentViewDirection(camera: ViewerCamera, target: Vector3, into: Vector3): Vector3;
|
|
582
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
|
+
type CapUniforms = {
|
|
742
|
+
uMask: {
|
|
743
|
+
value: Texture | null;
|
|
744
|
+
};
|
|
745
|
+
uResolution: {
|
|
746
|
+
value: Vector2;
|
|
747
|
+
};
|
|
748
|
+
uFill: {
|
|
749
|
+
value: Color;
|
|
750
|
+
};
|
|
751
|
+
uHatch: {
|
|
752
|
+
value: Color;
|
|
753
|
+
};
|
|
754
|
+
uOutline: {
|
|
755
|
+
value: Color;
|
|
756
|
+
};
|
|
757
|
+
uPitch: {
|
|
758
|
+
value: number;
|
|
759
|
+
};
|
|
760
|
+
uLine: {
|
|
761
|
+
value: number;
|
|
762
|
+
};
|
|
763
|
+
uThickness: {
|
|
764
|
+
value: number;
|
|
765
|
+
};
|
|
766
|
+
};
|
|
767
|
+
type CapMaterial = ShaderMaterial & {
|
|
768
|
+
uniforms: CapUniforms;
|
|
769
|
+
};
|
|
770
|
+
/**
|
|
771
|
+
* The render target the mask is drawn into. Sized later, to the drawing
|
|
772
|
+
* buffer, because that is what `gl_FragCoord` is measured in and a mask at
|
|
773
|
+
* any other size would put the outline beside the edge rather than on it.
|
|
774
|
+
*/
|
|
775
|
+
declare function createMaskTarget(): WebGLRenderTarget;
|
|
776
|
+
/**
|
|
777
|
+
* The cap's own material, filled only where the main pass's stencil says the
|
|
778
|
+
* plane is inside material. Unthemed until {@link applyCapTheme} is called.
|
|
779
|
+
*/
|
|
780
|
+
declare function createCapMaterial(mask: Texture): CapMaterial;
|
|
781
|
+
/** Re-themes a cap in place, and re-scales its hatch for the device pixel ratio. */
|
|
782
|
+
declare function applyCapTheme(material: CapMaterial, theme: ViewerTheme, dpr: number): void;
|
|
783
|
+
/**
|
|
784
|
+
* The two materials of the stencil pass: back faces count up, front faces
|
|
785
|
+
* count down, and what is left non-zero is where the plane is inside solid
|
|
786
|
+
* material. Nothing is written but the stencil.
|
|
787
|
+
*/
|
|
788
|
+
declare function createStencilMaterials(clip: Plane[]): [MeshBasicMaterial, MeshBasicMaterial];
|
|
789
|
+
/** The quad the mask pass draws where the stencil is set — white on black. */
|
|
790
|
+
declare function createMaskMaterial(): MeshBasicMaterial;
|
|
791
|
+
|
|
792
|
+
interface ArrowPlacement {
|
|
793
|
+
/** Where the arrow's tip sits: outside the box, on the direction's ray. */
|
|
794
|
+
readonly tip: Vector3;
|
|
795
|
+
readonly length: number;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Where an arrow for `direction` sits relative to the part.
|
|
799
|
+
*
|
|
800
|
+
* The exit distance is the smallest `halfExtent / |component|` over the axes the
|
|
801
|
+
* direction actually moves along — the first face of the box the ray leaves
|
|
802
|
+
* through. That formulation is what makes this work for **arbitrary unit
|
|
803
|
+
* vectors** rather than only for axes: real reports carry tilted directions,
|
|
804
|
+
* one of them a 36° five-axis setup, and an axis-aligned simplification would
|
|
805
|
+
* bury those arrows inside the part.
|
|
806
|
+
*/
|
|
807
|
+
declare function arrowPlacement(direction: Vec3, box: Box3): ArrowPlacement;
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* The bounds of the part, for the overlays that have to be sized against it.
|
|
811
|
+
*
|
|
812
|
+
* Measured on a frame rather than in an effect, because a Suspense-loaded mesh
|
|
813
|
+
* does not exist yet when the effects around it run — the same reason the
|
|
814
|
+
* viewer's opening frame waits. Measured once: an overlay that re-fitted itself
|
|
815
|
+
* while the part was being orbited would be a grid that breathes.
|
|
816
|
+
*
|
|
817
|
+
* Scene furniture is excluded, so the grid and the axes do not size each other.
|
|
818
|
+
*/
|
|
819
|
+
declare function useContentBox(): Box3;
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Telling a click apart from the end of a drag.
|
|
823
|
+
*
|
|
824
|
+
* The browser calls both a click: press, move, release over the same element
|
|
825
|
+
* fires `click` however far the pointer travelled in between. In a viewport
|
|
826
|
+
* that is the difference between "select this face" and "I have finished
|
|
827
|
+
* orbiting" — and the part is one mesh, so releasing over a different face is
|
|
828
|
+
* still the same element and still a click.
|
|
829
|
+
*
|
|
830
|
+
* Left unguarded, every orbit that happens to end over the part selects
|
|
831
|
+
* whatever it ended on, throwing away the selection the orbit was made to look
|
|
832
|
+
* at.
|
|
833
|
+
*/
|
|
834
|
+
/** How far a pointer may travel and still be a click, in CSS pixels. */
|
|
835
|
+
declare const TAP_SLOP = 4;
|
|
836
|
+
interface TapPoint {
|
|
837
|
+
readonly clientX: number;
|
|
838
|
+
readonly clientY: number;
|
|
839
|
+
}
|
|
840
|
+
/** Whether the pointer travelled far enough for this to be a drag. */
|
|
841
|
+
declare function movedFar(from: TapPoint, to: TapPoint, slop?: number): boolean;
|
|
842
|
+
interface TapTracker {
|
|
843
|
+
/** Whether the gesture ending at this event was a click rather than a drag. */
|
|
844
|
+
isTap(event: TapPoint): boolean;
|
|
845
|
+
dispose(): void;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Watches an element for the start of every gesture, so its end can be judged.
|
|
849
|
+
*
|
|
850
|
+
* Listens in the capture phase: whatever else handles the press — the camera
|
|
851
|
+
* controls, a drag handle — the start of the gesture still has to be recorded,
|
|
852
|
+
* and a listener that runs after `stopPropagation` runs never.
|
|
853
|
+
*/
|
|
854
|
+
declare function trackTaps(element: HTMLElement): TapTracker;
|
|
855
|
+
/**
|
|
856
|
+
* How long between two presses still reads as one gesture, in milliseconds.
|
|
857
|
+
*
|
|
858
|
+
* The browser's own `dblclick` threshold is a platform setting and is not
|
|
859
|
+
* readable from script, so a hand-paired gesture has to pick a number. 400ms is
|
|
860
|
+
* inside every default and outside a deliberate pair of separate clicks.
|
|
861
|
+
*/
|
|
862
|
+
declare const DOUBLE_TAP_MS = 400;
|
|
863
|
+
interface DoubleTapPoint extends TapPoint {
|
|
864
|
+
readonly timeStamp: number;
|
|
865
|
+
}
|
|
866
|
+
interface DoubleTapTracker {
|
|
867
|
+
/** Whether this press completes a double tap begun by the last one. */
|
|
868
|
+
isDouble(event: DoubleTapPoint): boolean;
|
|
869
|
+
/**
|
|
870
|
+
* Forgets the press being waited on, so the next one starts a fresh pair.
|
|
871
|
+
*
|
|
872
|
+
* For the caller that knows the gesture was interrupted by something this
|
|
873
|
+
* has no way to see — the pointer leaving the thing being clicked, most of
|
|
874
|
+
* all. Two presses either side of a trip to a panel are two clicks that
|
|
875
|
+
* happen to be quick, however close together the clock puts them.
|
|
876
|
+
*/
|
|
877
|
+
reset(): void;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Pairing presses into a double tap by hand.
|
|
881
|
+
*
|
|
882
|
+
* Needed because `dblclick` fires for the **primary button only** — there is no
|
|
883
|
+
* such event for the middle button, however many times it is pressed. So a
|
|
884
|
+
* middle-button gesture has to be assembled from single presses.
|
|
885
|
+
*
|
|
886
|
+
* Position matters as well as time: two presses at opposite corners inside the
|
|
887
|
+
* window are two clicks that happened to be quick, not one gesture, and
|
|
888
|
+
* treating them as a double would re-frame the view out from under somebody
|
|
889
|
+
* who was doing something else. The same {@link TAP_SLOP} the click guard uses.
|
|
890
|
+
*
|
|
891
|
+
* A completed double clears the state rather than leaving it, so three presses
|
|
892
|
+
* are one double and one single rather than two overlapping doubles.
|
|
893
|
+
*
|
|
894
|
+
* Time and position are all this can judge on, which is not enough on its own:
|
|
895
|
+
* clicking a face, pressing something in a panel and clicking the same face
|
|
896
|
+
* again is three gestures the clock cannot tell from two, and it is quick
|
|
897
|
+
* enough to land inside the window. {@link DoubleTapTracker.reset} is how the
|
|
898
|
+
* caller says the pair was broken by something it could see and this could not.
|
|
899
|
+
*/
|
|
900
|
+
declare function trackDoubleTaps(within?: number, slop?: number): DoubleTapTracker;
|
|
901
|
+
|
|
902
|
+
/** Whether the gesture ending at this event was a click rather than a drag. */
|
|
903
|
+
type TapGuard = (event: TapPoint) => boolean;
|
|
904
|
+
/**
|
|
905
|
+
* A click that is a click, for anything inside the canvas.
|
|
906
|
+
*
|
|
907
|
+
* Bound to the canvas element rather than to the object, because the gesture
|
|
908
|
+
* being judged started before any object knew about it.
|
|
909
|
+
*
|
|
910
|
+
* Under a `<Viewer>` this returns the tracker the scene already owns and
|
|
911
|
+
* attaches nothing. Called outside one — it is exported, so a consumer may use
|
|
912
|
+
* it in a scene of their own — it owns a tracker of its own instead. The effect
|
|
913
|
+
* is what differs, not the hooks: they run in the same order either way.
|
|
914
|
+
*/
|
|
915
|
+
declare function useTapGuard(): TapGuard;
|
|
916
|
+
|
|
917
|
+
interface GridSpec {
|
|
918
|
+
/** Cell size in part units (millimetres). */
|
|
919
|
+
readonly step: number;
|
|
920
|
+
/** Half-width of the grid, so it spans `2 × extent`. */
|
|
921
|
+
readonly extent: number;
|
|
922
|
+
/** The plane the grid sits on: the bottom of the part, snapped to a step. */
|
|
923
|
+
readonly z: number;
|
|
924
|
+
readonly center: Vector3;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Sizes a ground grid for a part.
|
|
928
|
+
*
|
|
929
|
+
* The step comes from a 1-2-5 progression rather than a fixed size, so the same
|
|
930
|
+
* code reads sensibly for a 12 mm insert and a 900 mm plate — the Engine emits
|
|
931
|
+
* millimetres but says nothing about scale.
|
|
932
|
+
*
|
|
933
|
+
* The plane is the *bottom* of the part, not `z = 0`: parts usually sit on
|
|
934
|
+
* `z = 0` and then the two agree, but one modelled about its centre would
|
|
935
|
+
* otherwise be sliced in half by its own grid.
|
|
936
|
+
*
|
|
937
|
+
* **Give this a measured box.** The plane is `box.min.z`, which is `+Infinity`
|
|
938
|
+
* for an empty one. Nothing else in the spec shows it — `getSize` and
|
|
939
|
+
* `getCenter` both short-circuit to zero for an empty `Box3` — so the step and
|
|
940
|
+
* the extent come back looking ordinary and {@link gridGeometry} then builds
|
|
941
|
+
* every vertex on a plane at infinity, which three.js reports as "Computed
|
|
942
|
+
* radius is NaN" once per mount.
|
|
943
|
+
*
|
|
944
|
+
* `useContentBox` hands back an empty box until the scene has been
|
|
945
|
+
* measured, so a caller pairing the two wants `if (box.isEmpty()) return null`
|
|
946
|
+
* ahead of this. That is what the package's own `Grid` does, and it is why it
|
|
947
|
+
* draws nothing on the opening frame rather than a grid nobody asked for.
|
|
948
|
+
*/
|
|
949
|
+
declare function gridSpec(box: Box3): GridSpec;
|
|
950
|
+
/**
|
|
951
|
+
* A ground grid on the part's Z-up base plane.
|
|
952
|
+
*
|
|
953
|
+
* Built directly rather than with `GridHelper`, which lays out on XZ for a Y-up
|
|
954
|
+
* world and has to be rotated into place — the Engine is Z-up, and a rotated
|
|
955
|
+
* helper is a thing to remember rather than a thing that is true.
|
|
956
|
+
*/
|
|
957
|
+
declare function gridGeometry(spec: GridSpec): BufferGeometry;
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* The lines between regions, and nothing else.
|
|
961
|
+
*
|
|
962
|
+
* `EdgesGeometry` draws an edge wherever two facets meet at more than some
|
|
963
|
+
* angle, which is a guess standing in for "is this a real edge". On a machined
|
|
964
|
+
* part the guess is wrong in both directions: a small bore tessellated into
|
|
965
|
+
* twelve facets has 30° between them and gets drawn as a nut, while a shallow
|
|
966
|
+
* chamfer meeting a wall at 12° gets no line at all.
|
|
967
|
+
*
|
|
968
|
+
* The report already knows. A region is one analytic surface, so an edge inside
|
|
969
|
+
* one is tessellation and an edge between two is a real boundary — the same
|
|
970
|
+
* fact that makes region-aware shading possible, used for the other half of
|
|
971
|
+
* what makes a part read as a part.
|
|
972
|
+
*
|
|
973
|
+
* With one qualification. The Engine splits a surface where that makes a better
|
|
974
|
+
* machining plan, and those splits are boundaries between regions without being
|
|
975
|
+
* edges of the part: a floor cut in two to be reached from two directions is
|
|
976
|
+
* still one face to look at. So the walk is over *visual surfaces* — regions
|
|
977
|
+
* grouped by the kernel's exact `splitOrigin` lineage — and a split leaves no
|
|
978
|
+
* line. Nothing about picking or features goes through that grouping.
|
|
979
|
+
*
|
|
980
|
+
* The mesh must be non-indexed, which `parsePartGeometry` guarantees.
|
|
981
|
+
*/
|
|
982
|
+
declare function regionEdgesGeometry(geometry: BufferGeometry, model: Pick<PartModel, 'regions' | 'regionIndex'>): BufferGeometry;
|
|
983
|
+
|
|
984
|
+
/** Region index → the visual surface it belongs to. */
|
|
985
|
+
type SurfaceOf = ReadonlyMap<number, number>;
|
|
986
|
+
/**
|
|
987
|
+
* The visual surface of each post-split region.
|
|
988
|
+
*
|
|
989
|
+
* The kernel divides B-rep faces to make machining relationships unambiguous.
|
|
990
|
+
* Those divisions remain meaningful to feature extraction and picking, but
|
|
991
|
+
* they are not physical edges. `splitOrigin` is the kernel's exact lineage
|
|
992
|
+
* relation: equal values mean the regions were one face before that analysis
|
|
993
|
+
* split. It is therefore the single grouping used for rendering, rather than
|
|
994
|
+
* attempting to infer continuations from mesh normals or surface kinds.
|
|
995
|
+
*/
|
|
996
|
+
declare function visualSurfaces(regions: readonly PartModelRegion[]): SurfaceOf;
|
|
997
|
+
|
|
583
998
|
/**
|
|
584
999
|
* Mouse and trackpad presets.
|
|
585
1000
|
*
|
|
@@ -705,12 +1120,26 @@ interface ViewerControls {
|
|
|
705
1120
|
* see a wall and then asks to zoom to it wants it closer, not re-oriented.
|
|
706
1121
|
*/
|
|
707
1122
|
frameBox(box: Box3): void;
|
|
1123
|
+
/**
|
|
1124
|
+
* Sets or clears the cut the viewer holds for itself — the one `<SectionTool>`
|
|
1125
|
+
* places and `<PartMesh>` follows when it is given no `section` of its own.
|
|
1126
|
+
*
|
|
1127
|
+
* `null` clears it. A `<PartMesh section>` is not affected: that cut is the
|
|
1128
|
+
* consumer's, and this one is the viewer's.
|
|
1129
|
+
*/
|
|
1130
|
+
setSection(options: SectionOptions | null): void;
|
|
708
1131
|
}
|
|
709
1132
|
interface ViewerHandle extends ViewerControls {
|
|
710
1133
|
}
|
|
711
1134
|
type VectorLike = Pick<Vector3, 'x' | 'y' | 'z'>;
|
|
712
1135
|
|
|
713
1136
|
declare const useViewerControls: () => ViewerControls;
|
|
1137
|
+
/**
|
|
1138
|
+
* The cut this viewer holds for itself. See `render/section-store.ts` for why
|
|
1139
|
+
* it is a store: `<SectionTool>` writes it and `<PartMesh>` reads it, and the
|
|
1140
|
+
* two are siblings.
|
|
1141
|
+
*/
|
|
1142
|
+
declare const useSectionStore: () => SectionStore;
|
|
714
1143
|
/** Re-aims the orbit, or `null` when this viewer has the gesture turned off. */
|
|
715
1144
|
type Retarget = ((point: VectorLike) => void) | null;
|
|
716
1145
|
/**
|
|
@@ -1081,4 +1510,4 @@ declare const ORBIT_TARGET_FADE_MS = 1000;
|
|
|
1081
1510
|
*/
|
|
1082
1511
|
declare function orbitTargetOpacity(sinceHold: number, fade?: number): number;
|
|
1083
1512
|
|
|
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 };
|
|
1513
|
+
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, 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, 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, rankOwners, regionAdjacency, regionEdgesGeometry, regionNormals, resolveSectionPlane, retargetPose, sameDirection, screenLength, screenPoint, snapAt, squaredUp, startPosition, targetBoundary, trackDoubleTaps, trackTaps, useContentBox, useRetarget, useSectionStore, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
|