@toolpath/viewer 0.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/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/chunk-7NQBV7EQ.js +2537 -0
- package/dist/engine/index.d.ts +118 -0
- package/dist/engine/index.js +30 -0
- package/dist/index.d.ts +637 -0
- package/dist/index.js +676 -0
- package/dist/normalize-B0HBvzGu.d.ts +868 -0
- package/package.json +68 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { V as Vec3, d as ViewerTheme, P as PartModel, e as PartModelRegion, f as ViewerCamera, g as ViewerView, h as Projection, T as TriangleRange, F as FeatureTag, R as RegionIndex, i as PartModelFeature, j as FeatureType } from './normalize-B0HBvzGu.js';
|
|
2
|
+
export { B as BuildPickInput, C as CAD_CAMERA_UP, k as CANDIDATE_WEIGHT, D as DEFAULT_FIT_MARGIN, l as DEFAULT_THEME, m as DIRECTION_COLORS, o as EXCLUDE_FROM_FRAME, E as EnginePart, p as FeatureHighlight, H as HANDLE_PIXELS, q as HIGHLIGHT_COLORS, r as HIGHLIGHT_WEIGHT, t as HOVER_WEIGHT, u as HighlightLayers, K as KnownFeatureType, N as NO_MODIFIERS, v as PERSPECTIVE_FOV, w as PICKED_SURFACE_LABEL, x as PartMesh, y as PartMeshProps, a as PartMeshRefs, z as PartObject, A as PartPick, G as PickModifiers, I as REGION_ATTRIBUTE, J as RegionHighlight, L as RegionPaint, S as SECTION_RENDER_ORDER, O as SceneBounds, Q as SectionAnchor, U as SectionBounds, W as SectionOptions, X as SectionPlacement, Y as SectionState, Z as SectionView, _ as ViewportSize, $ as applyHighlightLayers, a0 as applyProjection, a1 as aspectRatio, a2 as boundsFromBox, a3 as buildPick, a4 as buildRegionAttribute, a5 as buildRegionTexels, a6 as cadViewDirections, a7 as contentBounds, a8 as createPart, a9 as currentViewDirection, aa as defaultBounds, ab as directionColor, ac as dragPlane, ad as fitDistance, ae as focusForPick, n as normalizePartReport, af as orthographicHalfHeight, ag as perspectiveFitDistance, ah as pickedStartDepth, ai as resolveSectionPlane, aj as resolveTheme, ak as screenLength, al as sectionBounds, am as sectionConstant, an as sectionDepth, ao as sectionDepthConstant, ap as sectionDepthRange, aq as sectionFromPick, ar as sectionOffset, as as sectionPlane, s as smoothRegionNormals, at as startPosition, au as themesEqual, av as viewDirection } from './normalize-B0HBvzGu.js';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { RefObject, ReactNode, CSSProperties } from 'react';
|
|
5
|
+
import { BufferGeometry, CanvasTexture, Vector3, Box3 } from 'three';
|
|
6
|
+
import CameraControls from 'camera-controls';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The 26 standard views: the six faces of the cube, the twelve edges between
|
|
10
|
+
* them, and its eight corners.
|
|
11
|
+
*
|
|
12
|
+
* Ordered top/bottom, then front/back, then left/right, all the way through, so
|
|
13
|
+
* a caller has a rule to rely on.
|
|
14
|
+
*/
|
|
15
|
+
declare const VIEW_NAMES: readonly ["top", "bottom", "front", "back", "left", "right", "top-front", "top-back", "top-left", "top-right", "bottom-front", "bottom-back", "bottom-left", "bottom-right", "front-left", "front-right", "back-left", "back-right", "top-front-left", "top-front-right", "top-back-left", "top-back-right", "bottom-front-left", "bottom-front-right", "bottom-back-left", "bottom-back-right"];
|
|
16
|
+
type ViewName = (typeof VIEW_NAMES)[number];
|
|
17
|
+
/** A face, one of the chamfers between two faces, or a corner chamfer. */
|
|
18
|
+
type ViewKind = 'face' | 'edge' | 'corner';
|
|
19
|
+
/**
|
|
20
|
+
* Which way each view lies, as the sign of each axis in the Z-up frame the part
|
|
21
|
+
* data is authored in: **+Z is the top, −Y is the front, +X is the right**.
|
|
22
|
+
*
|
|
23
|
+
* Signs rather than unit vectors because these are numbers a reader can check.
|
|
24
|
+
* `top-front-left` is three components of ±1/√3 and nobody proofreads those.
|
|
25
|
+
*/
|
|
26
|
+
declare const VIEW_SIGNS: Record<ViewName, readonly [number, number, number]>;
|
|
27
|
+
/** How many axes a view is off: one for a face, two for an edge, three for a corner. */
|
|
28
|
+
declare function viewKind(name: ViewName): ViewKind;
|
|
29
|
+
/** The unit direction the camera sits in for a view. */
|
|
30
|
+
declare function viewVector(name: ViewName): Vec3;
|
|
31
|
+
/**
|
|
32
|
+
* Which way is up when looking from `direction`.
|
|
33
|
+
*
|
|
34
|
+
* Z is up for every view except the two that look straight down it, where an up
|
|
35
|
+
* vector parallel to the view is degenerate and the camera would have no
|
|
36
|
+
* defined roll. Those get ±Y, so the top view puts the front edge at the bottom
|
|
37
|
+
* of the screen and the bottom view mirrors it — the convention every CAD
|
|
38
|
+
* package uses, and the one the cube's own face labels are drawn to, since both
|
|
39
|
+
* come from here.
|
|
40
|
+
*/
|
|
41
|
+
declare function viewUp(direction: Vec3): Vec3;
|
|
42
|
+
/**
|
|
43
|
+
* Half-width of a face panel, where the cube's half-extent is 1 — so the
|
|
44
|
+
* chamfer taken off each edge is the remaining `1 - CHAMFER`.
|
|
45
|
+
*/
|
|
46
|
+
declare const CHAMFER = 0.5834;
|
|
47
|
+
/** One clickable panel of the cube: a face, an edge chamfer, or a corner. */
|
|
48
|
+
interface CubeZone {
|
|
49
|
+
readonly name: ViewName;
|
|
50
|
+
readonly kind: ViewKind;
|
|
51
|
+
/** The panel's outward normal, and the direction the camera moves to. */
|
|
52
|
+
readonly direction: Vec3;
|
|
53
|
+
/** Its polygon in cube space, wound counter-clockwise seen from outside. */
|
|
54
|
+
readonly polygon: readonly Vec3[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The chamfered cube, one planar polygon per view — a square for each face, a
|
|
58
|
+
* rectangle for each edge, a triangle for each corner. Pure, and the only
|
|
59
|
+
* description of the cube's shape: the panels, the outline and the hit test are
|
|
60
|
+
* all built from what this returns.
|
|
61
|
+
*/
|
|
62
|
+
declare function cubeZones(chamfer?: number): CubeZone[];
|
|
63
|
+
/** A zone's polygon as a flat, outward-facing triangle fan. */
|
|
64
|
+
declare function panelGeometry(zone: CubeZone): BufferGeometry;
|
|
65
|
+
/** The outline around every panel, as line segments. */
|
|
66
|
+
declare function cubeOutlineGeometry(zones: readonly CubeZone[]): BufferGeometry;
|
|
67
|
+
/**
|
|
68
|
+
* A face's name on a quad floating just proud of it.
|
|
69
|
+
*
|
|
70
|
+
* The quad is built from the face's own polygon in its stored order, which
|
|
71
|
+
* {@link cubeZones} lays out as (−right, −up), (+right, −up), (+right, +up),
|
|
72
|
+
* (−right, +up) — so mapping UVs onto it in that order puts the text the right
|
|
73
|
+
* way up for the camera pose {@link viewUp} chooses for the same view.
|
|
74
|
+
*/
|
|
75
|
+
declare function labelGeometry(zone: CubeZone): BufferGeometry;
|
|
76
|
+
/** A face name, drawn on a transparent canvas and sized to fit. */
|
|
77
|
+
declare function labelTexture(label: string, color: number): CanvasTexture;
|
|
78
|
+
|
|
79
|
+
interface GridProps {
|
|
80
|
+
/** Cell size in millimetres. Sized from the scene when omitted. */
|
|
81
|
+
step?: number;
|
|
82
|
+
extent?: number;
|
|
83
|
+
color?: string | number;
|
|
84
|
+
opacity?: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A ground grid on the part's base plane, sized to the part.
|
|
88
|
+
*
|
|
89
|
+
* Measured from whatever else is in the scene rather than given a fixed size:
|
|
90
|
+
* the Engine emits millimetres but says nothing about scale, and a grid of
|
|
91
|
+
* fixed cells is either invisible on a 900 mm plate or a solid wash under a
|
|
92
|
+
* 12 mm insert.
|
|
93
|
+
*/
|
|
94
|
+
declare const Grid: ({ step, extent, color, opacity }: GridProps) => react.JSX.Element;
|
|
95
|
+
interface AxesProps {
|
|
96
|
+
size?: number;
|
|
97
|
+
}
|
|
98
|
+
declare const Axes: ({ size }: AxesProps) => react.JSX.Element;
|
|
99
|
+
interface ViewCubeProps {
|
|
100
|
+
alignment?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
101
|
+
margin?: [number, number];
|
|
102
|
+
theme?: Partial<ViewerTheme>;
|
|
103
|
+
/** Called with the view a panel was clicked for, after the camera moves. */
|
|
104
|
+
onViewChange?: (view: ViewName) => void;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The orientation cube, in the corner of the viewport.
|
|
108
|
+
*
|
|
109
|
+
* Twenty-six clickable panels rather than six: the chamfers between faces are
|
|
110
|
+
* the edge and corner views, which is how you reach an isometric without
|
|
111
|
+
* dragging for it. A drei `GizmoViewport` gives three labelled axes and no
|
|
112
|
+
* views at all.
|
|
113
|
+
*/
|
|
114
|
+
declare const ViewCube: ({ alignment, margin, theme, onViewChange, }: ViewCubeProps) => react.JSX.Element;
|
|
115
|
+
|
|
116
|
+
interface NamedDirection {
|
|
117
|
+
readonly direction: Vec3;
|
|
118
|
+
readonly color: number;
|
|
119
|
+
readonly index: number;
|
|
120
|
+
}
|
|
121
|
+
interface DirectionArrowsProps {
|
|
122
|
+
/**
|
|
123
|
+
* The ways up the part can be held — a report's `candidateDirections`.
|
|
124
|
+
*
|
|
125
|
+
* The directions rather than the report: an arrow is drawn from a unit vector
|
|
126
|
+
* and the part's bounds, and nothing else about a part is needed to place one.
|
|
127
|
+
*/
|
|
128
|
+
directions: readonly Vec3[];
|
|
129
|
+
/**
|
|
130
|
+
* Shows one direction on its own. Choosing a direction is a way of asking
|
|
131
|
+
* about that direction, so the others leave rather than dimming: five faded
|
|
132
|
+
* arrows still cross the part, still hide surfaces behind them, and still
|
|
133
|
+
* read as choices when only one is being asked about.
|
|
134
|
+
*/
|
|
135
|
+
activeDirection?: number | null;
|
|
136
|
+
/**
|
|
137
|
+
* Shows one direction's arrow without scoping anything, for "the feature
|
|
138
|
+
* being read came from this way". Falls back to `activeDirection`.
|
|
139
|
+
*/
|
|
140
|
+
shownDirection?: number | null;
|
|
141
|
+
/**
|
|
142
|
+
* A direction being named, drawn while it is aimed. Not a candidate and not a
|
|
143
|
+
* selection: a way up that does not exist yet, so it is drawn over the part —
|
|
144
|
+
* an arrow being aimed that hides behind the geometry looks like it has
|
|
145
|
+
* stopped responding.
|
|
146
|
+
*/
|
|
147
|
+
previewDirection?: Vec3 | null;
|
|
148
|
+
/**
|
|
149
|
+
* Ways up that are held but were never candidates. A direction somebody named
|
|
150
|
+
* is as real as one the Engine proposed, and without an arrow it is a row in a
|
|
151
|
+
* list describing an orientation nothing on the part shows.
|
|
152
|
+
*/
|
|
153
|
+
namedDirections?: readonly NamedDirection[];
|
|
154
|
+
onPickDirection?: (index: number) => void;
|
|
155
|
+
theme?: Partial<ViewerTheme>;
|
|
156
|
+
visible?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One arrow per candidate direction, pointing at the part.
|
|
160
|
+
*
|
|
161
|
+
* Aimed *inward* on purpose: a machining direction is the direction the tool
|
|
162
|
+
* comes from, so an arrow flying toward the surface reads as the setup rather
|
|
163
|
+
* than as a surface normal.
|
|
164
|
+
*/
|
|
165
|
+
declare const DirectionArrows: ({ directions, activeDirection, shownDirection, previewDirection, namedDirections, onPickDirection, theme, visible, }: DirectionArrowsProps) => react.JSX.Element | null;
|
|
166
|
+
|
|
167
|
+
interface ArrowPlacement {
|
|
168
|
+
/** Where the arrow's tip sits: outside the box, on the direction's ray. */
|
|
169
|
+
readonly tip: Vector3;
|
|
170
|
+
readonly length: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Where an arrow for `direction` sits relative to the part.
|
|
174
|
+
*
|
|
175
|
+
* The exit distance is the smallest `halfExtent / |component|` over the axes the
|
|
176
|
+
* direction actually moves along — the first face of the box the ray leaves
|
|
177
|
+
* through. That formulation is what makes this work for **arbitrary unit
|
|
178
|
+
* vectors** rather than only for axes: real reports carry tilted directions,
|
|
179
|
+
* one of them a 36° five-axis setup, and an axis-aligned simplification would
|
|
180
|
+
* bury those arrows inside the part.
|
|
181
|
+
*/
|
|
182
|
+
declare function arrowPlacement(direction: Vec3, box: Box3): ArrowPlacement;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The bounds of the part, for the overlays that have to be sized against it.
|
|
186
|
+
*
|
|
187
|
+
* Measured on a frame rather than in an effect, because a Suspense-loaded mesh
|
|
188
|
+
* does not exist yet when the effects around it run — the same reason the
|
|
189
|
+
* viewer's opening frame waits. Measured once: an overlay that re-fitted itself
|
|
190
|
+
* while the part was being orbited would be a grid that breathes.
|
|
191
|
+
*
|
|
192
|
+
* Scene furniture is excluded, so the grid and the axes do not size each other.
|
|
193
|
+
*/
|
|
194
|
+
declare function useContentBox(): Box3;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Telling a click apart from the end of a drag.
|
|
198
|
+
*
|
|
199
|
+
* The browser calls both a click: press, move, release over the same element
|
|
200
|
+
* fires `click` however far the pointer travelled in between. In a viewport
|
|
201
|
+
* that is the difference between "select this face" and "I have finished
|
|
202
|
+
* orbiting" — and the part is one mesh, so releasing over a different face is
|
|
203
|
+
* still the same element and still a click.
|
|
204
|
+
*
|
|
205
|
+
* Left unguarded, every orbit that happens to end over the part selects
|
|
206
|
+
* whatever it ended on, throwing away the selection the orbit was made to look
|
|
207
|
+
* at.
|
|
208
|
+
*/
|
|
209
|
+
/** How far a pointer may travel and still be a click, in CSS pixels. */
|
|
210
|
+
declare const TAP_SLOP = 4;
|
|
211
|
+
interface TapPoint {
|
|
212
|
+
readonly clientX: number;
|
|
213
|
+
readonly clientY: number;
|
|
214
|
+
}
|
|
215
|
+
/** Whether the pointer travelled far enough for this to be a drag. */
|
|
216
|
+
declare function movedFar(from: TapPoint, to: TapPoint, slop?: number): boolean;
|
|
217
|
+
interface TapTracker {
|
|
218
|
+
/** Whether the gesture ending at this event was a click rather than a drag. */
|
|
219
|
+
isTap(event: TapPoint): boolean;
|
|
220
|
+
dispose(): void;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Watches an element for the start of every gesture, so its end can be judged.
|
|
224
|
+
*
|
|
225
|
+
* Listens in the capture phase: whatever else handles the press — the camera
|
|
226
|
+
* controls, a drag handle — the start of the gesture still has to be recorded,
|
|
227
|
+
* and a listener that runs after `stopPropagation` runs never.
|
|
228
|
+
*/
|
|
229
|
+
declare function trackTaps(element: HTMLElement): TapTracker;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A click that is a click, for anything inside the canvas.
|
|
233
|
+
*
|
|
234
|
+
* Bound to the canvas element rather than to the object, because the gesture
|
|
235
|
+
* being judged started before any object knew about it.
|
|
236
|
+
*/
|
|
237
|
+
declare function useTapGuard(): (event: TapPoint) => boolean;
|
|
238
|
+
|
|
239
|
+
interface GridSpec {
|
|
240
|
+
/** Cell size in part units (millimetres). */
|
|
241
|
+
readonly step: number;
|
|
242
|
+
/** Half-width of the grid, so it spans `2 × extent`. */
|
|
243
|
+
readonly extent: number;
|
|
244
|
+
/** The plane the grid sits on: the bottom of the part, snapped to a step. */
|
|
245
|
+
readonly z: number;
|
|
246
|
+
readonly center: Vector3;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Sizes a ground grid for a part.
|
|
250
|
+
*
|
|
251
|
+
* The step comes from a 1-2-5 progression rather than a fixed size, so the same
|
|
252
|
+
* code reads sensibly for a 12 mm insert and a 900 mm plate — the Engine emits
|
|
253
|
+
* millimetres but says nothing about scale.
|
|
254
|
+
*
|
|
255
|
+
* The plane is the *bottom* of the part, not `z = 0`: parts usually sit on
|
|
256
|
+
* `z = 0` and then the two agree, but one modelled about its centre would
|
|
257
|
+
* otherwise be sliced in half by its own grid.
|
|
258
|
+
*/
|
|
259
|
+
declare function gridSpec(box: Box3): GridSpec;
|
|
260
|
+
/**
|
|
261
|
+
* A ground grid on the part's Z-up base plane.
|
|
262
|
+
*
|
|
263
|
+
* Built directly rather than with `GridHelper`, which lays out on XZ for a Y-up
|
|
264
|
+
* world and has to be rotated into place — the Engine is Z-up, and a rotated
|
|
265
|
+
* helper is a thing to remember rather than a thing that is true.
|
|
266
|
+
*/
|
|
267
|
+
declare function gridGeometry(spec: GridSpec): BufferGeometry;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The lines between regions, and nothing else.
|
|
271
|
+
*
|
|
272
|
+
* `EdgesGeometry` draws an edge wherever two facets meet at more than some
|
|
273
|
+
* angle, which is a guess standing in for "is this a real edge". On a machined
|
|
274
|
+
* part the guess is wrong in both directions: a small bore tessellated into
|
|
275
|
+
* twelve facets has 30° between them and gets drawn as a nut, while a shallow
|
|
276
|
+
* chamfer meeting a wall at 12° gets no line at all.
|
|
277
|
+
*
|
|
278
|
+
* The report already knows. A region is one analytic surface, so an edge inside
|
|
279
|
+
* one is tessellation and an edge between two is a real boundary — the same
|
|
280
|
+
* fact that makes region-aware shading possible, used for the other half of
|
|
281
|
+
* what makes a part read as a part.
|
|
282
|
+
*
|
|
283
|
+
* With one qualification. The Engine splits a surface where that makes a better
|
|
284
|
+
* machining plan, and those splits are boundaries between regions without being
|
|
285
|
+
* edges of the part: a floor cut in two to be reached from two directions is
|
|
286
|
+
* still one flat floor. So the walk is over *visual surfaces* — regions grouped
|
|
287
|
+
* where they continue each other — and a split leaves no line. See
|
|
288
|
+
* `visualSurfaces`; nothing about picking or features goes through it.
|
|
289
|
+
*
|
|
290
|
+
* The mesh must be non-indexed, which `parsePartGeometry` guarantees.
|
|
291
|
+
*/
|
|
292
|
+
declare function regionEdgesGeometry(geometry: BufferGeometry, model: Pick<PartModel, 'regions' | 'regionIndex'>): BufferGeometry;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Which regions are one surface to look at.
|
|
296
|
+
*
|
|
297
|
+
* The Engine splits a surface where that makes a better machining plan — a
|
|
298
|
+
* floor cut in two so each half can be reached from a different direction, say.
|
|
299
|
+
* Those splits are real and features depend on them, but they are not edges of
|
|
300
|
+
* the part: a plane divided in two is still flat, and drawing the join or
|
|
301
|
+
* shading across it makes a clean model look faceted and creased where nothing
|
|
302
|
+
* creases.
|
|
303
|
+
*
|
|
304
|
+
* So this groups regions that continue each other, and the grouping is used for
|
|
305
|
+
* the two things that are about how the part *looks* — its edges and its
|
|
306
|
+
* shading. Nothing about picking, highlighting or features goes through here:
|
|
307
|
+
* a split is still two regions to click on and still two regions a feature can
|
|
308
|
+
* own, which is the whole reason the Engine made it.
|
|
309
|
+
*
|
|
310
|
+
* Two regions continue each other when they meet along an edge, are both
|
|
311
|
+
* **planes**, and are flat to within a degree of one another — which for a
|
|
312
|
+
* plane means they are the same plane.
|
|
313
|
+
*
|
|
314
|
+
* Planes only, and that is a limit of what the report says rather than caution.
|
|
315
|
+
* A region carries an `idx`, a `shapeKind`, an area and a triangle range: there
|
|
316
|
+
* is nothing in it that says which analytic surface a region was cut from. On a
|
|
317
|
+
* flat face that does not matter, because two coplanar planes meeting along an
|
|
318
|
+
* edge *are* one plane and no part has an edge there. On a curved one it
|
|
319
|
+
* matters entirely: a fillet running tangentially into a shaft and a fillet
|
|
320
|
+
* split down the middle look identical from the facets alone, and guessing
|
|
321
|
+
* between them either rubs out a line the part has or leaves one it does not.
|
|
322
|
+
* A line the part has is the worse of the two to lose, so curved boundaries are
|
|
323
|
+
* all drawn.
|
|
324
|
+
*
|
|
325
|
+
* The exact version of this wants the Engine to say which surface a region came
|
|
326
|
+
* from. Until it does, this is the half that can be proved.
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* How far two planes may disagree across a shared edge and still be one plane.
|
|
330
|
+
*
|
|
331
|
+
* A degree, which is a rounding error rather than a judgement: a split is
|
|
332
|
+
* exactly coplanar, and anything a part actually turns through is a chamfer at
|
|
333
|
+
* fifteen degrees or more.
|
|
334
|
+
*/
|
|
335
|
+
declare const CONTINUES_WITHIN: number;
|
|
336
|
+
/** Region index → the surface it belongs to, by region `idx`. */
|
|
337
|
+
type SurfaceOf = ReadonlyMap<number, number>;
|
|
338
|
+
declare function visualSurfaces(geometry: BufferGeometry, regions: readonly PartModelRegion[]): SurfaceOf;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Mouse and trackpad presets.
|
|
342
|
+
*
|
|
343
|
+
* - `toolpath` — left-drag orbits, right- and middle-drag pan. The product
|
|
344
|
+
* default.
|
|
345
|
+
* - `fusion` — middle-drag and two-finger scroll pan, shift makes them orbit,
|
|
346
|
+
* pinch zooms. Matches Fusion 360, which is what most of our users have open
|
|
347
|
+
* in the other window.
|
|
348
|
+
*/
|
|
349
|
+
type ControlScheme = 'toolpath' | 'fusion';
|
|
350
|
+
type ExtendedCameraControlsOptions = {
|
|
351
|
+
/**
|
|
352
|
+
* Orbit past the poles instead of stopping there. When enabled the up vector
|
|
353
|
+
* is re-derived from the view each frame, so there is no gimbal stop.
|
|
354
|
+
*/
|
|
355
|
+
readonly freeOrbit?: boolean | undefined;
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* `CameraControls` with free orbit, camera-relative up, and the Fusion wheel
|
|
359
|
+
* scheme.
|
|
360
|
+
*
|
|
361
|
+
* Two departures from the legacy implementation, both deliberate:
|
|
362
|
+
*
|
|
363
|
+
* - It does **not** override `connect`/`disconnect`. Legacy declared them as
|
|
364
|
+
* arrow-function class fields, which shadowed the base methods of the same
|
|
365
|
+
* name — so `connect()` added a pointer listener but never did what the base
|
|
366
|
+
* class's `connect` does, and the base's own connection came from the
|
|
367
|
+
* constructor instead. The extra listeners live on `attach`/`detach` here,
|
|
368
|
+
* and the base methods are left alone.
|
|
369
|
+
* - Modifier state is tracked internally. Legacy read Shift and Control from a
|
|
370
|
+
* React hook and reapplied the whole preset on every keypress, which coupled
|
|
371
|
+
* the controls to the component tree for two booleans.
|
|
372
|
+
*/
|
|
373
|
+
declare class ExtendedCameraControls extends CameraControls {
|
|
374
|
+
#private;
|
|
375
|
+
constructor(camera: ViewerCamera, domElement: HTMLElement, options?: ExtendedCameraControlsOptions);
|
|
376
|
+
get freeOrbit(): boolean;
|
|
377
|
+
get scheme(): ControlScheme;
|
|
378
|
+
/** Adds the listeners this subclass owns, on top of the base connection. */
|
|
379
|
+
attach(): void;
|
|
380
|
+
detach(): void;
|
|
381
|
+
dispose(): void;
|
|
382
|
+
/**
|
|
383
|
+
* Reapplies the current preset. The Viewer calls this after a projection
|
|
384
|
+
* change too, because the correct wheel action differs between an
|
|
385
|
+
* orthographic and a perspective camera.
|
|
386
|
+
*/
|
|
387
|
+
applyScheme(scheme: ControlScheme): void;
|
|
388
|
+
setFreeOrbit(freeOrbit: boolean): void;
|
|
389
|
+
/** Returns the camera to Z-up, the orientation the part data is authored in. */
|
|
390
|
+
resetUpVector(): void;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
interface CadCameraControlsProps {
|
|
394
|
+
/** Filled with the controls once they exist, for imperative framing. */
|
|
395
|
+
controlsRef: RefObject<ExtendedCameraControls | null>;
|
|
396
|
+
scheme?: ControlScheme;
|
|
397
|
+
/**
|
|
398
|
+
* Orbit past the poles instead of stopping there. On by default: a CAD view
|
|
399
|
+
* that sticks when it reaches straight-down is the single most-reported thing
|
|
400
|
+
* about an orbit control.
|
|
401
|
+
*/
|
|
402
|
+
freeOrbit?: boolean;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Mounts `camera-controls` against the R3F camera and canvas.
|
|
406
|
+
*
|
|
407
|
+
* `frameloop="demand"` means no frame runs unless something asks for one, so
|
|
408
|
+
* the controls' own `control` and `update` events drive `invalidate` — without
|
|
409
|
+
* that the view would move only when React happened to render. The controls are
|
|
410
|
+
* also registered as R3F's default `controls`, which is how anything else in
|
|
411
|
+
* the scene reaches the orbit target.
|
|
412
|
+
*/
|
|
413
|
+
declare const CadCameraControls: ({ controlsRef, scheme, freeOrbit, }: CadCameraControlsProps) => null;
|
|
414
|
+
|
|
415
|
+
interface ViewerControls {
|
|
416
|
+
/** Frames the part without changing the current viewing direction. */
|
|
417
|
+
fit(): void;
|
|
418
|
+
/** Returns to the opening view and frames the part. */
|
|
419
|
+
reset(): void;
|
|
420
|
+
setView(view: ViewerView): void;
|
|
421
|
+
/**
|
|
422
|
+
* Frames the part from an arbitrary direction — a unit vector from the part
|
|
423
|
+
* toward the camera.
|
|
424
|
+
*
|
|
425
|
+
* The named views are the six a keyboard shortcut reaches; the orientation
|
|
426
|
+
* cube offers twenty-six, and the twenty that are not axis-aligned have no
|
|
427
|
+
* names worth inventing.
|
|
428
|
+
*/
|
|
429
|
+
setViewDirection(direction: {
|
|
430
|
+
x: number;
|
|
431
|
+
y: number;
|
|
432
|
+
z: number;
|
|
433
|
+
}): void;
|
|
434
|
+
/**
|
|
435
|
+
* Frames arbitrary bounds from the direction being looked from, for zooming
|
|
436
|
+
* to one feature rather than to the whole part.
|
|
437
|
+
*
|
|
438
|
+
* Keeps the viewing direction on purpose: somebody who has turned the part to
|
|
439
|
+
* see a wall and then asks to zoom to it wants it closer, not re-oriented.
|
|
440
|
+
*/
|
|
441
|
+
frameBox(box: Box3): void;
|
|
442
|
+
}
|
|
443
|
+
interface ViewerHandle extends ViewerControls {
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
declare const useViewerControls: () => ViewerControls;
|
|
447
|
+
interface ViewerProps {
|
|
448
|
+
children?: ReactNode;
|
|
449
|
+
className?: string;
|
|
450
|
+
style?: CSSProperties;
|
|
451
|
+
/**
|
|
452
|
+
* Perspective by default. Orthographic is what a machinist reads a part in —
|
|
453
|
+
* parallel edges stay parallel, so two features the same size measure the
|
|
454
|
+
* same size wherever they sit.
|
|
455
|
+
*/
|
|
456
|
+
projection?: Projection;
|
|
457
|
+
/**
|
|
458
|
+
* `toolpath` — left-drag orbits, right-drag pans. `fusion` — middle-drag and
|
|
459
|
+
* two-finger scroll pan, shift makes them orbit, pinch zooms; it matches
|
|
460
|
+
* Fusion 360, which is what most users have open in the other window.
|
|
461
|
+
*/
|
|
462
|
+
controls?: ControlScheme;
|
|
463
|
+
/** Orbit past the poles instead of stopping there. On by default. */
|
|
464
|
+
freeOrbit?: boolean;
|
|
465
|
+
/** Lighting and background. The part's own colours are tuned against this rig. */
|
|
466
|
+
theme?: Partial<ViewerTheme>;
|
|
467
|
+
/**
|
|
468
|
+
* A click that hit nothing in the scene — not the part, not an arrow, not a
|
|
469
|
+
* section handle. The usual meaning is "put the selection down".
|
|
470
|
+
*/
|
|
471
|
+
onPointerMissed?: () => void;
|
|
472
|
+
}
|
|
473
|
+
declare const Viewer: react.ForwardRefExoticComponent<ViewerProps & react.RefAttributes<ViewerHandle>>;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* A report that cannot be turned into a `PartModel`.
|
|
477
|
+
*
|
|
478
|
+
* Contract mismatches are caught at normalization rather than deep in the
|
|
479
|
+
* renderer. A region table that does not tile the mesh would otherwise surface
|
|
480
|
+
* as mysterious mis-picking — that is, as a *shader* bug — hours away from its
|
|
481
|
+
* cause.
|
|
482
|
+
*/
|
|
483
|
+
declare class PartReportFormatError extends Error {
|
|
484
|
+
readonly name = "PartReportFormatError";
|
|
485
|
+
/** Every problem found, not just the first. */
|
|
486
|
+
readonly issues: readonly string[];
|
|
487
|
+
constructor(issues: readonly string[]);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* A report from a kernel older than the one this package targets.
|
|
491
|
+
*
|
|
492
|
+
* `0.2.0` identified features by a dense `featureIndex` and published no
|
|
493
|
+
* `regions[]`; `0.3.0` replaced that with an opaque `featureTag` and a region
|
|
494
|
+
* table. The two are mutually incompatible on feature identity, so this package
|
|
495
|
+
* targets one version and fails loudly rather than carrying a permanent
|
|
496
|
+
* compatibility shim.
|
|
497
|
+
*/
|
|
498
|
+
declare class UnsupportedKernelVersionError extends Error {
|
|
499
|
+
readonly name = "UnsupportedKernelVersionError";
|
|
500
|
+
readonly kernelVersion: string;
|
|
501
|
+
readonly minimumKernelVersion: string;
|
|
502
|
+
constructor(kernelVersion: string, minimumKernelVersion: string);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** The minimum a region must expose to be indexed. */
|
|
506
|
+
interface IndexableRegion {
|
|
507
|
+
readonly idx: number;
|
|
508
|
+
readonly triangles: TriangleRange;
|
|
509
|
+
}
|
|
510
|
+
/** The minimum a feature must expose to be indexed. */
|
|
511
|
+
interface IndexableFeature {
|
|
512
|
+
readonly tag: FeatureTag;
|
|
513
|
+
readonly regionIdxs: readonly number[];
|
|
514
|
+
}
|
|
515
|
+
interface BuildRegionIndexInput {
|
|
516
|
+
readonly regions: readonly IndexableRegion[];
|
|
517
|
+
readonly features: readonly IndexableFeature[];
|
|
518
|
+
readonly triangleCount: number;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Builds the `feature ↔ region ↔ triangle` index, validating the region table
|
|
522
|
+
* on the way through.
|
|
523
|
+
*
|
|
524
|
+
* The index is three typed arrays plus two maps. Picking is a binary search
|
|
525
|
+
* over region starts — no per-triangle lookup table, no material per feature,
|
|
526
|
+
* and no allocation on the pointer-move path.
|
|
527
|
+
*
|
|
528
|
+
* Validation is deliberately strict and happens **once**, here. Regions are
|
|
529
|
+
* guaranteed to tile `[0, triangleCount)` completely, so a gap, an overlap, or
|
|
530
|
+
* a range past the end of the mesh means the report is broken or the report and
|
|
531
|
+
* the mesh do not belong together. Failing at load turns that into one clear
|
|
532
|
+
* error instead of a highlight landing on the wrong surface much later.
|
|
533
|
+
*
|
|
534
|
+
* Emission order is *not* validated — the regions are sorted defensively rather
|
|
535
|
+
* than trusting the order they arrived in. What must hold is that the sorted
|
|
536
|
+
* ranges tile the mesh.
|
|
537
|
+
*/
|
|
538
|
+
declare function buildRegionIndex(input: BuildRegionIndexInput): RegionIndex;
|
|
539
|
+
|
|
540
|
+
interface DirectionGroup {
|
|
541
|
+
/**
|
|
542
|
+
* Position in `candidateDirections`, or `-1` for the group holding features
|
|
543
|
+
* whose direction is not among them — which no observed report produces, but
|
|
544
|
+
* which must not silently drop features if one ever does.
|
|
545
|
+
*/
|
|
546
|
+
readonly index: number;
|
|
547
|
+
readonly direction: Vec3;
|
|
548
|
+
readonly features: readonly PartModelFeature[];
|
|
549
|
+
}
|
|
550
|
+
declare function sameDirection(a: Vec3, b: Vec3): boolean;
|
|
551
|
+
/** The index of a direction in `candidateDirections`, or `-1`. */
|
|
552
|
+
declare function directionIndexOf(model: Pick<PartModel, 'candidateDirections'>, direction: Vec3): number;
|
|
553
|
+
/**
|
|
554
|
+
* Features by machining direction, in `candidateDirections` order.
|
|
555
|
+
*
|
|
556
|
+
* Every feature's `machiningDirection` is one of `candidateDirections` — zero
|
|
557
|
+
* exceptions across 1122 features on three parts — so the unmatched group is
|
|
558
|
+
* defensive rather than expected. Groups are returned even when empty, since a
|
|
559
|
+
* direction with no features is still a direction the part can be set up in.
|
|
560
|
+
*/
|
|
561
|
+
declare function groupByDirection(model: PartModel): readonly DirectionGroup[];
|
|
562
|
+
/**
|
|
563
|
+
* A direction as a label: `+Z` when it is an axis, three decimals otherwise.
|
|
564
|
+
*
|
|
565
|
+
* Not every direction is axis-aligned — real parts report tilted ones, a 36°
|
|
566
|
+
* five-axis setup among them — so the general form is the fallback rather than
|
|
567
|
+
* an error case.
|
|
568
|
+
*/
|
|
569
|
+
declare function directionLabel(direction: Vec3): string;
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Which way each face points, taken off the mesh.
|
|
573
|
+
*
|
|
574
|
+
* The report says which triangles belong to a region and how big it is; it does
|
|
575
|
+
* not say which way it faces. That is in the geometry, and it is what any
|
|
576
|
+
* question of the form "could this be cut from over there" starts with.
|
|
577
|
+
*
|
|
578
|
+
* Area-weighted, so a region made of one large triangle and a sliver reads as
|
|
579
|
+
* the large one. A perfectly flat region gives its own normal exactly; a curved
|
|
580
|
+
* one gives an average, which is honest — a bore's wall does not point one way,
|
|
581
|
+
* and a single vector for it is a simplification whoever uses it should know
|
|
582
|
+
* about.
|
|
583
|
+
*/
|
|
584
|
+
declare function regionNormals(geometry: BufferGeometry, regions: readonly PartModelRegion[]): Map<number, Vec3>;
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Feature types from most specific to least, for resolving a viewport click.
|
|
588
|
+
*
|
|
589
|
+
* Nothing geometric can separate a region's owners — two `wall` features on the
|
|
590
|
+
* same physical face from different directions reference an *identical* region
|
|
591
|
+
* set, so they have identical area by construction. Specificity is the only
|
|
592
|
+
* ordering left, and it is a judgement about intent: a hole is what someone
|
|
593
|
+
* means when they click a hole's wall.
|
|
594
|
+
*
|
|
595
|
+
* `profile` ranks last, alone. It is a boundary contour rather than a machined
|
|
596
|
+
* surface, so it should never win a click against the surface it traces — and
|
|
597
|
+
* because every direction's profile overlaps most of that direction's regions,
|
|
598
|
+
* this one rule resolves every in-direction collision on the cube.
|
|
599
|
+
*/
|
|
600
|
+
declare const FEATURE_TYPE_RANKS: readonly (readonly FeatureType[])[];
|
|
601
|
+
declare function featureTypeRank(type: FeatureType): number;
|
|
602
|
+
interface RankingContext {
|
|
603
|
+
/**
|
|
604
|
+
* The active machining direction. Narrows a region's owners to two, one, or
|
|
605
|
+
* **none** — the empty case is real, and is reported as "nothing here in this
|
|
606
|
+
* direction" rather than as a pick that missed.
|
|
607
|
+
*/
|
|
608
|
+
readonly activeDirection?: Vec3 | null;
|
|
609
|
+
/**
|
|
610
|
+
* A unit vector from the part toward the camera. The owner whose machining
|
|
611
|
+
* direction most nearly faces the viewer wins: looking down at the cube,
|
|
612
|
+
* `(0,0,1)` resolves to `+Z:face` rather than to a `±Y:wall` reading.
|
|
613
|
+
*/
|
|
614
|
+
readonly viewDirection?: Vec3 | null;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Orders a region's owning features for the single-selection case.
|
|
618
|
+
*
|
|
619
|
+
* A *default*, never a claim of correctness — which is why it must never be the
|
|
620
|
+
* only way to reach a feature. Every hover and pick carries the full owner set
|
|
621
|
+
* alongside the ranked one, the panel can be scoped to the candidates, and
|
|
622
|
+
* repeated clicks cycle (see {@link cycleOwner}).
|
|
623
|
+
*
|
|
624
|
+
* With an active direction the result is **filtered**, not merely reordered: a
|
|
625
|
+
* region the direction cannot reach yields an empty list.
|
|
626
|
+
*/
|
|
627
|
+
declare function rankOwners(model: PartModel, owners: readonly FeatureTag[], context?: RankingContext): readonly FeatureTag[];
|
|
628
|
+
/** The ranked pick, or `null` when the scope holds no owner at all. */
|
|
629
|
+
declare function bestOwner(model: PartModel, owners: readonly FeatureTag[], context?: RankingContext): FeatureTag | null;
|
|
630
|
+
/**
|
|
631
|
+
* The next owner after `current`, wrapping — the standard CAD escape hatch for
|
|
632
|
+
* an ambiguous click. `null` cycles in from the start; an owner that is not in
|
|
633
|
+
* the list restarts rather than dead-ends.
|
|
634
|
+
*/
|
|
635
|
+
declare function cycleOwner(owners: readonly FeatureTag[], current: FeatureTag | null): FeatureTag | null;
|
|
636
|
+
|
|
637
|
+
export { type ArrowPlacement, Axes, type AxesProps, CHAMFER, CONTINUES_WITHIN, CadCameraControls, type CadCameraControlsProps, type ControlScheme, type CubeZone, DirectionArrows, type DirectionArrowsProps, type DirectionGroup, ExtendedCameraControls, type ExtendedCameraControlsOptions, FEATURE_TYPE_RANKS, FeatureTag, FeatureType, Grid, type GridProps, type GridSpec, type NamedDirection, PartModel, PartModelFeature, PartModelRegion, PartReportFormatError, Projection, type RankingContext, RegionIndex, type SurfaceOf, TAP_SLOP, type TapPoint, type TapTracker, TriangleRange, UnsupportedKernelVersionError, VIEW_NAMES, VIEW_SIGNS, Vec3, ViewCube, type ViewCubeProps, type ViewKind, type ViewName, Viewer, ViewerCamera, type ViewerControls, type ViewerHandle, type ViewerProps, ViewerTheme, ViewerView, arrowPlacement, bestOwner, buildRegionIndex, cubeOutlineGeometry, cubeZones, cycleOwner, directionIndexOf, directionLabel, featureTypeRank, gridGeometry, gridSpec, groupByDirection, labelGeometry, labelTexture, movedFar, panelGeometry, rankOwners, regionEdgesGeometry, regionNormals, sameDirection, trackTaps, useContentBox, useTapGuard, useViewerControls, viewKind, viewUp, viewVector, visualSurfaces };
|