@toolpath/viewer 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  CAD_CAMERA_UP,
3
3
  CANDIDATE_WEIGHT,
4
+ CHAMFER,
4
5
  CONE_AXIS,
5
6
  CadCameraControls,
6
7
  DEFAULT_FIT_MARGIN,
7
8
  DEFAULT_THEME,
8
9
  DIRECTION_COLORS,
10
+ DOUBLE_TAP_MS,
9
11
  EXCLUDE_FROM_FRAME,
10
12
  EnginePart,
11
13
  ExtendedCameraControls,
@@ -16,7 +18,17 @@ import {
16
18
  HIGHLIGHT_COLORS,
17
19
  HIGHLIGHT_WEIGHT,
18
20
  HOVER_WEIGHT,
21
+ MAX_FRAME_RATIO,
22
+ MIN_FRAME_RATIO,
19
23
  NO_MODIFIERS,
24
+ ORBIT_TARGET_COLOR,
25
+ ORBIT_TARGET_FADE_MS,
26
+ ORBIT_TARGET_FLASH_MS,
27
+ ORBIT_TARGET_PIXELS,
28
+ ORBIT_TARGET_RING_COLOR,
29
+ ORBIT_TARGET_RING_OPACITY,
30
+ ORBIT_TARGET_RING_PIXELS,
31
+ ORBIT_TARGET_RING_WIDTH,
20
32
  PERSPECTIVE_FOV,
21
33
  PICKED_SURFACE_LABEL,
22
34
  PartMesh,
@@ -27,7 +39,10 @@ import {
27
39
  SectionView,
28
40
  TAP_SLOP,
29
41
  UnsupportedKernelVersionError,
42
+ VIEW_NAMES,
43
+ VIEW_SIGNS,
30
44
  Viewer,
45
+ adaptedUp,
31
46
  applyHighlightLayers,
32
47
  applyProjection,
33
48
  arrowPlacement,
@@ -39,8 +54,11 @@ import {
39
54
  buildRegionIndex,
40
55
  buildRegionTexels,
41
56
  cadViewDirections,
57
+ cameraLimits,
42
58
  contentBounds,
43
59
  createPart,
60
+ cubeOutlineGeometry,
61
+ cubeZones,
44
62
  currentViewDirection,
45
63
  cycleOwner,
46
64
  defaultBounds,
@@ -52,15 +70,21 @@ import {
52
70
  fitDistance,
53
71
  focusForPick,
54
72
  groupByDirection,
73
+ labelGeometry,
74
+ labelTexture,
55
75
  movedFar,
56
76
  normalizePartReport,
77
+ orbitTargetOpacity,
57
78
  orthographicHalfHeight,
79
+ panelGeometry,
58
80
  perspectiveFitDistance,
59
81
  pickedStartDepth,
60
82
  rankOwners,
83
+ regionAdjacency,
61
84
  regionEdgesGeometry,
62
85
  resolveSectionPlane,
63
86
  resolveTheme,
87
+ retargetPose,
64
88
  sameDirection,
65
89
  screenLength,
66
90
  sectionBounds,
@@ -72,15 +96,22 @@ import {
72
96
  sectionOffset,
73
97
  sectionPlane,
74
98
  smoothRegionNormals,
99
+ squaredUp,
75
100
  startPosition,
101
+ targetBoundary,
76
102
  themesEqual,
103
+ trackDoubleTaps,
77
104
  trackTaps,
78
105
  useContentBox,
106
+ useRetarget,
79
107
  useTapGuard,
80
108
  useViewerControls,
81
109
  viewDirection,
110
+ viewKind,
111
+ viewUp,
112
+ viewVector,
82
113
  visualSurfaces
83
- } from "./chunk-RICG6UVH.js";
114
+ } from "./chunk-4BU4QZCL.js";
84
115
 
85
116
  // src/primitives.tsx
86
117
  import { GizmoHelper } from "@react-three/drei";
@@ -125,233 +156,6 @@ function gridGeometry(spec) {
125
156
  return geometry;
126
157
  }
127
158
 
128
- // src/render/view-cube.ts
129
- import {
130
- BufferGeometry as BufferGeometry2,
131
- CanvasTexture,
132
- Float32BufferAttribute as Float32BufferAttribute2,
133
- SRGBColorSpace,
134
- Vector3 as Vector32
135
- } from "three";
136
- var VIEW_NAMES = [
137
- // The six faces.
138
- "top",
139
- "bottom",
140
- "front",
141
- "back",
142
- "left",
143
- "right",
144
- // The twelve edges.
145
- "top-front",
146
- "top-back",
147
- "top-left",
148
- "top-right",
149
- "bottom-front",
150
- "bottom-back",
151
- "bottom-left",
152
- "bottom-right",
153
- "front-left",
154
- "front-right",
155
- "back-left",
156
- "back-right",
157
- // The eight corners.
158
- "top-front-left",
159
- "top-front-right",
160
- "top-back-left",
161
- "top-back-right",
162
- "bottom-front-left",
163
- "bottom-front-right",
164
- "bottom-back-left",
165
- "bottom-back-right"
166
- ];
167
- var VIEW_SIGNS = {
168
- top: [0, 0, 1],
169
- bottom: [0, 0, -1],
170
- front: [0, -1, 0],
171
- back: [0, 1, 0],
172
- left: [-1, 0, 0],
173
- right: [1, 0, 0],
174
- "top-front": [0, -1, 1],
175
- "top-back": [0, 1, 1],
176
- "top-left": [-1, 0, 1],
177
- "top-right": [1, 0, 1],
178
- "bottom-front": [0, -1, -1],
179
- "bottom-back": [0, 1, -1],
180
- "bottom-left": [-1, 0, -1],
181
- "bottom-right": [1, 0, -1],
182
- "front-left": [-1, -1, 0],
183
- "front-right": [1, -1, 0],
184
- "back-left": [-1, 1, 0],
185
- "back-right": [1, 1, 0],
186
- "top-front-left": [-1, -1, 1],
187
- "top-front-right": [1, -1, 1],
188
- "top-back-left": [-1, 1, 1],
189
- "top-back-right": [1, 1, 1],
190
- "bottom-front-left": [-1, -1, -1],
191
- "bottom-front-right": [1, -1, -1],
192
- "bottom-back-left": [-1, 1, -1],
193
- "bottom-back-right": [1, 1, -1]
194
- };
195
- function viewKind(name) {
196
- const off = VIEW_SIGNS[name].filter((sign) => sign !== 0).length;
197
- if (off === 1) return "face";
198
- if (off === 2) return "edge";
199
- return "corner";
200
- }
201
- var DIRECTIONS = new Map(
202
- VIEW_NAMES.map((name) => {
203
- const [x, y, z] = VIEW_SIGNS[name];
204
- const length = Math.hypot(x, y, z);
205
- return [name, { x: x / length, y: y / length, z: z / length }];
206
- })
207
- );
208
- function viewVector(name) {
209
- return DIRECTIONS.get(name) ?? { x: 0, y: 0, z: 1 };
210
- }
211
- function viewUp(direction) {
212
- if (direction.x === 0 && direction.y === 0) {
213
- return { x: 0, y: direction.z > 0 ? 1 : -1, z: 0 };
214
- }
215
- return { x: 0, y: 0, z: 1 };
216
- }
217
- var CHAMFER = 0.5834;
218
- function vec(v) {
219
- return { x: v.x, y: v.y, z: v.z };
220
- }
221
- function cubeZones(chamfer = CHAMFER) {
222
- const AXES = [0, 1, 2];
223
- return VIEW_NAMES.map((name) => {
224
- const kind = viewKind(name);
225
- const direction = viewVector(name);
226
- const [sx, sy, sz] = VIEW_SIGNS[name];
227
- const sign = (axis) => VIEW_SIGNS[name][axis] ?? 0;
228
- const normal = new Vector32(direction.x, direction.y, direction.z);
229
- let polygon;
230
- if (kind === "face") {
231
- const up = viewUp(direction);
232
- const upVector = new Vector32(up.x, up.y, up.z);
233
- const right = normal.clone().negate().cross(upVector).normalize();
234
- const square = [
235
- [-1, -1],
236
- [1, -1],
237
- [1, 1],
238
- [-1, 1]
239
- ];
240
- polygon = square.map(
241
- ([u, v]) => normal.clone().addScaledVector(right, u * chamfer).addScaledVector(upVector, v * chamfer)
242
- );
243
- } else if (kind === "edge") {
244
- const a = AXES.find((axis) => sign(axis) !== 0) ?? 0;
245
- const b = [...AXES].reverse().find((axis) => sign(axis) !== 0) ?? 0;
246
- const c = AXES.find((axis) => sign(axis) === 0) ?? 0;
247
- const rectangle = [
248
- [1, chamfer, -chamfer],
249
- [1, chamfer, chamfer],
250
- [chamfer, 1, chamfer],
251
- [chamfer, 1, -chamfer]
252
- ];
253
- polygon = rectangle.map(
254
- ([onA, onB, onC]) => new Vector32().setComponent(a, sign(a) * onA).setComponent(b, sign(b) * onB).setComponent(c, onC)
255
- );
256
- } else {
257
- const triangle = [
258
- [1, chamfer, chamfer],
259
- [chamfer, 1, chamfer],
260
- [chamfer, chamfer, 1]
261
- ];
262
- polygon = triangle.map(([onX, onY, onZ]) => new Vector32(sx * onX, sy * onY, sz * onZ));
263
- }
264
- const [first, second, third] = polygon;
265
- if (first && second && third) {
266
- const facing = new Vector32().subVectors(second, first).cross(new Vector32().subVectors(third, first));
267
- if (facing.dot(normal) < 0) polygon.reverse();
268
- }
269
- return { name, kind, direction, polygon: polygon.map(vec) };
270
- });
271
- }
272
- function panelGeometry(zone) {
273
- const positions = [];
274
- const normals = [];
275
- const [first] = zone.polygon;
276
- const geometry = new BufferGeometry2();
277
- if (!first) return geometry;
278
- for (let index = 1; index + 1 < zone.polygon.length; index += 1) {
279
- const b = zone.polygon[index];
280
- const c = zone.polygon[index + 1];
281
- if (!b || !c) continue;
282
- positions.push(first.x, first.y, first.z, b.x, b.y, b.z, c.x, c.y, c.z);
283
- for (let vertex = 0; vertex < 3; vertex += 1) {
284
- normals.push(zone.direction.x, zone.direction.y, zone.direction.z);
285
- }
286
- }
287
- geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
288
- geometry.setAttribute("normal", new Float32BufferAttribute2(normals, 3));
289
- return geometry;
290
- }
291
- function cubeOutlineGeometry(zones) {
292
- const positions = [];
293
- for (const zone of zones) {
294
- for (let index = 0; index < zone.polygon.length; index += 1) {
295
- const from = zone.polygon[index];
296
- const to = zone.polygon[(index + 1) % zone.polygon.length];
297
- if (!from || !to) continue;
298
- positions.push(from.x, from.y, from.z, to.x, to.y, to.z);
299
- }
300
- }
301
- const geometry = new BufferGeometry2();
302
- geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
303
- return geometry;
304
- }
305
- function labelGeometry(zone) {
306
- const positions = [];
307
- const uvs = [];
308
- const corners = [0, 1, 2, 0, 2, 3];
309
- const corner = [
310
- [0, 0],
311
- [1, 0],
312
- [1, 1],
313
- [0, 1]
314
- ];
315
- for (const index of corners) {
316
- const point = zone.polygon[index];
317
- const uv = corner[index];
318
- if (!point || !uv) continue;
319
- positions.push(
320
- point.x + zone.direction.x * 4e-3,
321
- point.y + zone.direction.y * 4e-3,
322
- point.z + zone.direction.z * 4e-3
323
- );
324
- uvs.push(uv[0] ?? 0, uv[1] ?? 0);
325
- }
326
- const geometry = new BufferGeometry2();
327
- geometry.setAttribute("position", new Float32BufferAttribute2(positions, 3));
328
- geometry.setAttribute("uv", new Float32BufferAttribute2(uvs, 2));
329
- return geometry;
330
- }
331
- function labelTexture(label, color) {
332
- const size = 128;
333
- const canvas = document.createElement("canvas");
334
- canvas.width = size;
335
- canvas.height = size;
336
- const context = canvas.getContext("2d");
337
- if (context) {
338
- const text = label.toUpperCase();
339
- const hex = `#${color.toString(16).padStart(6, "0")}`;
340
- context.font = `600 ${size / 4}px system-ui, sans-serif`;
341
- const width = context.measureText("BOTTOM").width || 1;
342
- const scale = Math.min(1, size * 0.78 / width);
343
- context.font = `600 ${size / 4 * scale}px system-ui, sans-serif`;
344
- context.textAlign = "center";
345
- context.textBaseline = "middle";
346
- context.fillStyle = hex;
347
- context.fillText(text, size / 2, size / 2);
348
- }
349
- const texture = new CanvasTexture(canvas);
350
- texture.colorSpace = SRGBColorSpace;
351
- texture.needsUpdate = true;
352
- return texture;
353
- }
354
-
355
159
  // src/primitives.tsx
356
160
  import { jsx, jsxs } from "react/jsx-runtime";
357
161
  var FURNITURE = { [EXCLUDE_FROM_FRAME]: true };
@@ -460,7 +264,7 @@ var CubePanels = ({
460
264
  // src/direction-arrows.tsx
461
265
  import { useThree as useThree2 } from "@react-three/fiber";
462
266
  import { useMemo as useMemo2 } from "react";
463
- import { Quaternion, Vector3 as Vector33 } from "three";
267
+ import { Quaternion, Vector3 as Vector32 } from "three";
464
268
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
465
269
  var FURNITURE2 = { [EXCLUDE_FROM_FRAME]: true };
466
270
  var DirectionArrows = ({
@@ -476,6 +280,11 @@ var DirectionArrows = ({
476
280
  const box = useContentBox();
477
281
  const resolved = useMemo2(() => resolveTheme(theme), [theme]);
478
282
  const shown = shownDirection === void 0 ? activeDirection : shownDirection;
283
+ const drawn = (index) => {
284
+ if (shown === null) return true;
285
+ if (typeof shown === "number") return shown === index;
286
+ return shown.includes(index);
287
+ };
479
288
  if (!visible) return null;
480
289
  return (
481
290
  // Excluded from framing: the arrows are deliberately placed outside the
@@ -483,7 +292,7 @@ var DirectionArrows = ({
483
292
  // part small in the middle of them.
484
293
  /* @__PURE__ */ jsxs2("group", { userData: FURNITURE2, children: [
485
294
  directions.map((direction, index) => {
486
- if (shown !== null && shown !== index) return null;
295
+ if (!drawn(index)) return null;
487
296
  return /* @__PURE__ */ jsx2(
488
297
  Arrow,
489
298
  {
@@ -517,7 +326,7 @@ var Arrow = ({ direction, box, color, opacity, onPick, onTop = false }) => {
517
326
  const placement = arrowPlacement(direction, box);
518
327
  const aim = new Quaternion().setFromUnitVectors(
519
328
  CONE_AXIS,
520
- new Vector33(direction.x, direction.y, direction.z).normalize()
329
+ new Vector32(direction.x, direction.y, direction.z).normalize()
521
330
  );
522
331
  return { tip: placement.tip, length: placement.length, quaternion: aim };
523
332
  }, [box, direction]);
@@ -584,6 +393,7 @@ export {
584
393
  DEFAULT_FIT_MARGIN,
585
394
  DEFAULT_THEME,
586
395
  DIRECTION_COLORS,
396
+ DOUBLE_TAP_MS,
587
397
  DirectionArrows,
588
398
  EXCLUDE_FROM_FRAME,
589
399
  EnginePart,
@@ -594,7 +404,17 @@ export {
594
404
  HIGHLIGHT_COLORS,
595
405
  HIGHLIGHT_WEIGHT,
596
406
  HOVER_WEIGHT,
407
+ MAX_FRAME_RATIO,
408
+ MIN_FRAME_RATIO,
597
409
  NO_MODIFIERS,
410
+ ORBIT_TARGET_COLOR,
411
+ ORBIT_TARGET_FADE_MS,
412
+ ORBIT_TARGET_FLASH_MS,
413
+ ORBIT_TARGET_PIXELS,
414
+ ORBIT_TARGET_RING_COLOR,
415
+ ORBIT_TARGET_RING_OPACITY,
416
+ ORBIT_TARGET_RING_PIXELS,
417
+ ORBIT_TARGET_RING_WIDTH,
598
418
  PERSPECTIVE_FOV,
599
419
  PICKED_SURFACE_LABEL,
600
420
  PartMesh,
@@ -608,6 +428,7 @@ export {
608
428
  VIEW_SIGNS,
609
429
  ViewCube,
610
430
  Viewer,
431
+ adaptedUp,
611
432
  applyHighlightLayers,
612
433
  applyProjection,
613
434
  arrowPlacement,
@@ -619,6 +440,7 @@ export {
619
440
  buildRegionIndex,
620
441
  buildRegionTexels,
621
442
  cadViewDirections,
443
+ cameraLimits,
622
444
  contentBounds,
623
445
  createPart,
624
446
  cubeOutlineGeometry,
@@ -640,15 +462,18 @@ export {
640
462
  labelTexture,
641
463
  movedFar,
642
464
  normalizePartReport,
465
+ orbitTargetOpacity,
643
466
  orthographicHalfHeight,
644
467
  panelGeometry,
645
468
  perspectiveFitDistance,
646
469
  pickedStartDepth,
647
470
  rankOwners,
471
+ regionAdjacency,
648
472
  regionEdgesGeometry,
649
473
  regionNormals,
650
474
  resolveSectionPlane,
651
475
  resolveTheme,
476
+ retargetPose,
652
477
  sameDirection,
653
478
  screenLength,
654
479
  sectionBounds,
@@ -660,10 +485,14 @@ export {
660
485
  sectionOffset,
661
486
  sectionPlane,
662
487
  smoothRegionNormals,
488
+ squaredUp,
663
489
  startPosition,
490
+ targetBoundary,
664
491
  themesEqual,
492
+ trackDoubleTaps,
665
493
  trackTaps,
666
494
  useContentBox,
495
+ useRetarget,
667
496
  useTapGuard,
668
497
  useViewerControls,
669
498
  viewDirection,
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { Group, Mesh, LineSegments, Box3, Plane, BufferGeometry, Camera, Vector3, OrthographicCamera, PerspectiveCamera, Object3D } from 'three';
2
+ import { Group, Mesh, LineSegments, Box3, Plane, BufferGeometry, Camera, Vector3 } from 'three';
3
3
 
4
4
  /**
5
5
  * The normalized part — the only thing the renderer consumes.
@@ -215,7 +215,7 @@ declare const HIGHLIGHT_COLORS: {
215
215
  * of the palette — it is an identity, not a ranking, which is why the colors
216
216
  * are unordered and deliberately not a scale.
217
217
  */
218
- declare const DIRECTION_COLORS: readonly [3900150, 1357990, 14239471, 440020, 6660877, 15485081, 6583435, 1096065, 6514417];
218
+ declare const DIRECTION_COLORS: readonly [3900150, 889992, 14239471, 6809849, 6660877, 15485081, 9741240, 4906624, 1981066];
219
219
  declare const DEFAULT_THEME: ViewerTheme;
220
220
  declare function resolveTheme(overrides?: Partial<ViewerTheme>): ViewerTheme;
221
221
  /**
@@ -303,11 +303,15 @@ declare function buildRegionAttribute(model: RegionTable, texels: Map<number, nu
303
303
  /**
304
304
  * Builds the renderable part.
305
305
  *
306
- * Takes exclusive use of `geometry`: it adds {@link REGION_ATTRIBUTE} to it and
306
+ * Takes working use of `geometry`: it adds {@link REGION_ATTRIBUTE} to it and
307
307
  * removes that again on dispose, but never disposes the geometry itself — the
308
308
  * loader's caller owns it, and a viewer destroying an object it was handed is a
309
- * good way to break a cache that is legitimately sharing it. One geometry backs
310
- * one part; sharing it between two parts at once is unsupported.
309
+ * good way to break a cache that is legitimately sharing it.
310
+ *
311
+ * Two parts on one geometry is still not a thing to *render*, but it is a state
312
+ * that happens: a report changing identity rebuilds the part against the same
313
+ * cached mesh, and for a moment both exist. So dispose removes the attribute
314
+ * only if it is still the one this part set — see the note where it is built.
311
315
  */
312
316
  declare function createPart(model: PartModel, geometry: BufferGeometry, theme: ViewerTheme): PartObject;
313
317
 
@@ -430,6 +434,21 @@ interface PartPick {
430
434
  /** The surface's outward normal in world space — the plane under the cursor. */
431
435
  readonly normal: readonly [number, number, number];
432
436
  readonly modifiers: PickModifiers;
437
+ /**
438
+ * Whether this click completed a double click on the part.
439
+ *
440
+ * Reported rather than interpreted, for the same reason the modifiers are:
441
+ * what a second click means belongs to the app. A viewer that decided —
442
+ * withholding the pick so a double click could not disturb the selection —
443
+ * would be right for a list that walks through a face's readings and wrong
444
+ * for an editor where clicking a face twice puts it in and takes it out
445
+ * again. Both are ordinary, and only the app knows which one it is showing.
446
+ *
447
+ * The viewport still acts on the gesture itself: `retargetOnDoubleClick`
448
+ * re-aims the orbit, which is a question about the view rather than about the
449
+ * part, and is nobody else's to answer.
450
+ */
451
+ readonly doubled: boolean;
433
452
  }
434
453
  /** A unit vector from the part toward the camera, for the owner ranking. */
435
454
  declare function viewDirection(camera: Camera, target: Vector3): Vec3;
@@ -448,6 +467,8 @@ interface BuildPickInput {
448
467
  */
449
468
  readonly activeDirection?: number | null;
450
469
  readonly viewDirection?: Vec3 | null;
470
+ /** Whether this click completed a double click. Defaults to `false`. */
471
+ readonly doubled?: boolean;
451
472
  }
452
473
  declare function buildPick(input: BuildPickInput): PartPick;
453
474
  /**
@@ -459,86 +480,6 @@ declare function buildPick(input: BuildPickInput): PartPick;
459
480
  */
460
481
  declare function focusForPick(pick: PartPick, previousRegion: number | null, previousFocus: FeatureTag | null): FeatureTag | null;
461
482
 
462
- type Projection = 'orthographic' | 'perspective';
463
- type ViewerCamera = OrthographicCamera | PerspectiveCamera;
464
- interface ViewportSize {
465
- readonly width: number;
466
- readonly height: number;
467
- }
468
- /** Vertical field of view, in degrees, for the perspective camera. */
469
- declare const PERSPECTIVE_FOV = 30;
470
- /** Padding around the framed bounds, as a multiple of its radius. */
471
- declare const DEFAULT_FIT_MARGIN = 1.2;
472
- /** Marks scene furniture — grid, axes — that the camera should not frame. */
473
- declare const EXCLUDE_FROM_FRAME = "viewerExcludeFromFrame";
474
- /**
475
- * What the camera frames: a bounding *sphere*, not a box.
476
- *
477
- * A sphere makes framing rotation-invariant — the part stays fully visible from
478
- * every angle, and a resize never needs to know the current pose. Framing
479
- * per-axis box dimensions instead means recomputing the camera distance on
480
- * every resize, and still clipping on some orientations.
481
- */
482
- interface SceneBounds {
483
- readonly center: Vector3;
484
- readonly radius: number;
485
- }
486
- /**
487
- * Bounds for a scene with nothing in it. A viewer is mounted before it has a
488
- * part, and a camera with a zero-size frustum renders nothing at all.
489
- */
490
- declare function defaultBounds(): SceneBounds;
491
- /**
492
- * A container can be laid out at zero size, and a degenerate aspect ratio
493
- * produces a `NaN` projection matrix that never recovers. Fall back to square.
494
- */
495
- declare function aspectRatio(size: ViewportSize): number;
496
- declare function boundsFromBox(box: Box3): SceneBounds;
497
- /**
498
- * The bounds of everything worth framing under `root`.
499
- *
500
- * Skips objects flagged with {@link EXCLUDE_FROM_FRAME} and their descendants:
501
- * a 100 mm grid around a 6 mm part would otherwise frame the grid, and the part
502
- * would be a speck.
503
- */
504
- declare function contentBounds(root: Object3D, into: Box3): SceneBounds;
505
- /**
506
- * The distance at which a sphere of `radius` fits inside a perspective frustum.
507
- * Uses the narrower of the two field-of-view angles, so a portrait viewport
508
- * frames on width and a landscape one on height.
509
- */
510
- declare function perspectiveFitDistance(fovDegrees: number, aspect: number, radius: number): number;
511
- /**
512
- * Half-height of an orthographic frustum that fits a sphere of `radius`. A
513
- * portrait viewport grows the height so the width still clears the sphere.
514
- */
515
- declare function orthographicHalfHeight(aspect: number, radius: number): number;
516
- declare function fitDistance(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): number;
517
- declare function startPosition(projection: Projection, size: ViewportSize, bounds: SceneBounds, margin?: number): Vector3;
518
- /**
519
- * Points an existing camera's frustum at `bounds` for the current viewport.
520
- * Pose is untouched — that belongs to the controls.
521
- */
522
- declare function applyProjection(camera: ViewerCamera, size: ViewportSize, bounds: SceneBounds, margin?: number): void;
523
- /**
524
- * The world-up convention. The part data is Z-up (millimetres, no conversion) —
525
- * not the glTF-conventional Y-up — so the camera has to say so explicitly.
526
- */
527
- declare const CAD_CAMERA_UP: Vector3;
528
- /** Named viewing directions, as unit vectors from the part toward the camera. */
529
- declare const cadViewDirections: {
530
- readonly front: Vector3;
531
- readonly back: Vector3;
532
- readonly left: Vector3;
533
- readonly right: Vector3;
534
- readonly top: Vector3;
535
- readonly bottom: Vector3;
536
- readonly isometric: Vector3;
537
- };
538
- type ViewerView = keyof typeof cadViewDirections;
539
- /** The current orbit direction, so Fit can retain the direction being looked from. */
540
- declare function currentViewDirection(camera: ViewerCamera, target: Vector3, into: Vector3): Vector3;
541
-
542
483
  /**
543
484
  * Render order. The stencil pass must precede the cap, and the part must draw
544
485
  * after both or it overwrites the cap it is supposed to be capped by. The part
@@ -635,14 +576,6 @@ declare function sectionFromPick(surface: {
635
576
  declare function pickedStartDepth(box: Box3): number;
636
577
  /** The plane a cut sits on, for a placement or a swept offset. */
637
578
  declare function sectionPlane(box: Box3, normal: Vec3, offset: number, into?: Plane): Plane;
638
- /**
639
- * A world length that covers `pixels` on screen at `point`.
640
- *
641
- * A handle sized in world units is a thumbnail on a plate and a wall on an
642
- * insert; the whole reason it is measured this way is that it is a control
643
- * rather than part of the model.
644
- */
645
- declare function screenLength(camera: ViewerCamera, point: Vector3, viewport: ViewportSize, pixels: number): number;
646
579
  /**
647
580
  * The plane a drag is projected onto: the one containing the handle's axis and
648
581
  * facing the camera as squarely as it can.
@@ -772,6 +705,15 @@ interface PartMeshProps {
772
705
  * real change, so echoing it into state is safe.
773
706
  */
774
707
  onSectionChange?: (state: SectionState) => void;
708
+ /**
709
+ * Which faces touch which, once the mesh is in.
710
+ *
711
+ * The consumer cannot work this out: it has a region table with areas and
712
+ * shape kinds, and the topology only exists in the geometry. Handed over
713
+ * rather than queried, because it is computed once per mesh and the consumer
714
+ * needs it to answer questions about faces it has not clicked yet.
715
+ */
716
+ onAdjacency?: (adjacency: ReadonlyMap<number, ReadonlySet<number>>) => void;
775
717
  /**
776
718
  * A feature to frame. Framed when it changes, so setting it to the feature
777
719
  * already framed does nothing — a zoom is a request, not a state to hold.
@@ -801,7 +743,7 @@ interface PartMeshProps {
801
743
  * React render at all: the hovered face lives in a ref, and routing it through
802
744
  * state is what this rewrite exists to stop doing.
803
745
  */
804
- declare const PartMesh: ({ model, geometry, selection, candidates, highlights, regionHighlights, pickedRegions, hoveredFeatureIds, activeDirection, section, onSectionChange, focusFeature, onHover, onPick, theme, showEdges, }: PartMeshProps) => react.JSX.Element;
746
+ declare const PartMesh: ({ model, geometry, selection, candidates, highlights, regionHighlights, pickedRegions, hoveredFeatureIds, activeDirection, section, onSectionChange, onAdjacency, focusFeature, onHover, onPick, theme, showEdges, }: PartMeshProps) => react.JSX.Element;
805
747
 
806
748
  interface EnginePartProps extends Omit<PartMeshProps, 'model' | 'geometry'> {
807
749
  /**
@@ -876,4 +818,4 @@ declare function normalizePartReport(report: unknown): PartModel;
876
818
  */
877
819
  declare function assertSupportedKernelVersion(kernelVersion: string): void;
878
820
 
879
- export { applyHighlightLayers as $, type PartPick as A, type BuildPickInput as B, CAD_CAMERA_UP as C, DEFAULT_FIT_MARGIN as D, EnginePart as E, type FeatureTag as F, type PickModifiers as G, HANDLE_PIXELS as H, REGION_ATTRIBUTE as I, type RegionHighlight as J, type KnownFeatureType as K, type RegionPaint as L, MIN_KERNEL_VERSION as M, NO_MODIFIERS as N, type SceneBounds as O, type PartModel as P, type SectionAnchor as Q, type RegionIndex as R, SECTION_RENDER_ORDER as S, type TriangleRange as T, type SectionBounds as U, type Vec3 as V, type SectionOptions as W, type SectionPlacement as X, type SectionState as Y, SectionView as Z, type ViewportSize as _, type PartMeshRefs as a, applyProjection as a0, aspectRatio as a1, boundsFromBox as a2, buildPick as a3, buildRegionAttribute as a4, buildRegionTexels as a5, cadViewDirections as a6, contentBounds as a7, createPart as a8, currentViewDirection as a9, defaultBounds as aa, directionColor as ab, dragPlane as ac, fitDistance as ad, focusForPick as ae, orthographicHalfHeight as af, perspectiveFitDistance as ag, pickedStartDepth as ah, resolveSectionPlane as ai, resolveTheme as aj, screenLength as ak, sectionBounds as al, sectionConstant as am, sectionDepth as an, sectionDepthConstant as ao, sectionDepthRange as ap, sectionFromPick as aq, sectionOffset as ar, sectionPlane as as, startPosition as at, themesEqual as au, viewDirection as av, type EnginePartProps as b, assertSupportedKernelVersion as c, type ViewerTheme as d, type PartModelRegion as e, type ViewerCamera as f, type ViewerView as g, type Projection as h, type PartModelFeature as i, type FeatureType as j, CANDIDATE_WEIGHT as k, DEFAULT_THEME as l, DIRECTION_COLORS as m, normalizePartReport as n, EXCLUDE_FROM_FRAME as o, type FeatureHighlight as p, HIGHLIGHT_COLORS as q, HIGHLIGHT_WEIGHT as r, smoothRegionNormals as s, HOVER_WEIGHT as t, type HighlightLayers as u, PERSPECTIVE_FOV as v, PICKED_SURFACE_LABEL as w, PartMesh as x, type PartMeshProps as y, type PartObject as z };
821
+ export { resolveSectionPlane as $, type SectionOptions as A, type BuildPickInput as B, CANDIDATE_WEIGHT as C, DEFAULT_THEME as D, EnginePart as E, type FeatureTag as F, type SectionPlacement as G, HANDLE_PIXELS as H, type SectionState as I, SectionView as J, type KnownFeatureType as K, applyHighlightLayers as L, MIN_KERNEL_VERSION as M, NO_MODIFIERS as N, buildPick as O, type PartModel as P, buildRegionAttribute as Q, type RegionIndex as R, SECTION_RENDER_ORDER as S, type TriangleRange as T, buildRegionTexels as U, type Vec3 as V, createPart as W, directionColor as X, dragPlane as Y, focusForPick as Z, pickedStartDepth as _, type PartMeshRefs as a, resolveTheme as a0, sectionBounds as a1, sectionConstant as a2, sectionDepth as a3, sectionDepthConstant as a4, sectionDepthRange as a5, sectionFromPick as a6, sectionOffset as a7, sectionPlane as a8, themesEqual as a9, viewDirection as aa, type EnginePartProps as b, assertSupportedKernelVersion as c, type ViewerTheme as d, type PartModelRegion as e, type PartModelFeature as f, type FeatureType as g, DIRECTION_COLORS as h, type FeatureHighlight as i, HIGHLIGHT_COLORS as j, HIGHLIGHT_WEIGHT as k, HOVER_WEIGHT as l, type HighlightLayers as m, normalizePartReport as n, PICKED_SURFACE_LABEL as o, PartMesh as p, type PartMeshProps as q, type PartObject as r, smoothRegionNormals as s, type PartPick as t, type PickModifiers as u, REGION_ATTRIBUTE as v, type RegionHighlight as w, type RegionPaint as x, type SectionAnchor as y, type SectionBounds as z };
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@toolpath/viewer",
3
- "version": "0.3.1",
3
+ "version": "1.0.0",
4
4
  "description": "React Three Fiber viewer for Toolpath Engine part reports",
5
5
  "license": "MIT",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
- "url": "https://github.com/toolpath/toolpath.git",
11
+ "url": "https://github.com/toolpath/ui-packages.git",
9
12
  "directory": "packages/viewer"
10
13
  },
11
14
  "homepage": "https://developers.toolpath.com",
12
15
  "bugs": {
13
- "url": "https://github.com/toolpath/toolpath/issues"
16
+ "url": "https://github.com/toolpath/ui-packages/issues"
14
17
  },
15
18
  "publishConfig": {
16
19
  "access": "public",
@@ -62,6 +65,7 @@
62
65
  },
63
66
  "scripts": {
64
67
  "build": "tsup src/index.ts src/engine/index.ts --format esm --dts --clean --external react --external react-dom --external three --external @react-three/fiber --external @react-three/drei",
68
+ "build:watch": "tsup src/index.ts src/engine/index.ts --format esm --dts --watch --external react --external react-dom --external three --external @react-three/fiber --external @react-three/drei",
65
69
  "check-types": "tsc --noEmit",
66
70
  "test": "vitest run"
67
71
  }