@toolpath/viewer 2.3.3 → 2.6.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 CHANGED
@@ -306,6 +306,7 @@ the part opaque. For multiselection, update your own tag array and avoid duplica
306
306
  | `Viewer` | Canvas, camera, lighting, controls, initial framing, and viewer-owned section state. |
307
307
  | `EnginePart` | Validated Engine report plus cached mesh loading; imports from `/engine`. |
308
308
  | `PartMesh` | Your normalized model and prepared geometry; selection, paint, picking, sections. |
309
+ | `ExplodedPart` | Supplied surface groups, translations, colors, and controlled explosion amount. |
309
310
  | `ViewCube` | 26 orientation targets: faces, edges, and corners. |
310
311
  | `Grid`, `Axes` | Reference geometry excluded from camera framing. |
311
312
  | `DirectionArrows` | Candidate and optional named/preview machining directions. |
@@ -331,6 +332,7 @@ the part opaque. For multiselection, update your own tag array and avoid duplica
331
332
  | `showOrbitTarget` | `false` | Show the pivot marker. |
332
333
  | `theme` | Default lighting | Override lighting; part/cube/tool colors have their own theme props. |
333
334
  | `onPointerMissed` | — | Empty-space primary click, with drag guarding; use it to clear selection. |
335
+ | `webglUnavailable` | Default message | Content of the alert shown instead of the canvas when WebGL is unavailable. |
334
336
  | `style`, `className` | Full-size wrapper | Set dimensions and background on the wrapper or parent. |
335
337
  | `ref` | — | `ViewerHandle` for controls outside the canvas. |
336
338
 
@@ -340,6 +342,13 @@ the part opaque. For multiselection, update your own tag array and avoid duplica
340
342
  Named views are `top`, `bottom`, `front`, `back`, `left`, `right`, and `isometric`.
341
343
  Direction vectors point from the part toward the camera.
342
344
 
345
+ `Viewer` checks for WebGL2 once per page, on the client. If the browser will not create a context
346
+ (hardware acceleration off or the GPU blocklisted), it renders a `role="alert"`
347
+ element (`viewer-webgl-unavailable`, `data-viewer-webgl-unavailable="true"`) in
348
+ place of the canvas, asking the user to turn on graphics acceleration; children are
349
+ not rendered. Pass `webglUnavailable` to replace that message. Server rendering assumes
350
+ WebGL is available; the check runs on the client after hydration.
351
+
343
352
  #### CAD controls
344
353
 
345
354
  | Preset | Rotate | Pan | Zoom |
@@ -375,6 +384,7 @@ fails or is absent. It does not fetch a report or start an Engine job.
375
384
  | `activeDirection` | Candidate-direction index; `null`/omitted means all directions. |
376
385
  | `display`, `showEdges` | `solid` or `wireframe`; outlines default on. Wireframe always shows semantic face boundaries, not triangle diagonals. |
377
386
  | `sharpCorners` | `{ features: tags }` for externally flagged features, or `null`. |
387
+ | `threads` | `{ threads: marks }` to draw threads in their holes, or `null`. See "Showing threads". |
378
388
  | `section`, `onSectionChange` | Controlled section options and change reporting. Omit `section` to follow the viewer's section store. |
379
389
  | `onAdjacency` | Receive touching-region sets for the prepared mesh. |
380
390
  | `theme` | Partial `ViewerTheme` for the part. |
@@ -385,6 +395,118 @@ has `best === null`. `owners` still carries all owners. `directionHighlights(mod
385
395
  index)` produces region paint matching `DirectionArrows`; it does not hide geometry
386
396
  or establish that an unpainted face is impossible to machine.
387
397
 
398
+ ### `<ExplodedPart>`
399
+
400
+ Renders application-defined surface pieces inside `<Viewer>` or an R3F canvas. Import
401
+ `ExplodedPart`, `ExplodedPartProps`, `ExplodeGroup`, `ExplodeGroupIds`, `ExplodeInput`,
402
+ `ExplodePick`, `ExplodeRegionColors`, `createExplodeAmount`, and `ExplodeAmount` from
403
+ `@toolpath/viewer`. Version 2.5.0 does not contain these exports.
404
+
405
+ The application supplies the pieces and their translations. The renderer neither selects
406
+ feature interpretations nor chooses machining directions, setups, or an ownership policy for
407
+ overlapping surfaces. It reads no planning data and makes no API requests.
408
+
409
+ ```tsx
410
+ import { useMemo, useState } from 'react'
411
+ import { ExplodedPart, Viewer, type ExplodeGroup, type PartModel } from '@toolpath/viewer'
412
+ import type { BufferGeometry } from 'three'
413
+
414
+ const ExplodeView = ({
415
+ model,
416
+ geometry,
417
+ groups,
418
+ amount,
419
+ }: {
420
+ model: PartModel
421
+ geometry: BufferGeometry
422
+ groups: readonly ExplodeGroup[]
423
+ amount: number
424
+ }) => {
425
+ const [selected, select] = useState<string | null>(null)
426
+ const [hovered, hover] = useState<string | null>(null)
427
+ const explode = useMemo(() => ({ groups, amount }), [groups, amount])
428
+ return (
429
+ <Viewer>
430
+ <ExplodedPart
431
+ model={model}
432
+ geometry={geometry}
433
+ explode={explode}
434
+ selected={selected}
435
+ hovered={hovered}
436
+ onPick={(pick) => select(pick.explodeGroupId)}
437
+ onHover={(pick) => hover(pick?.explodeGroupId ?? null)}
438
+ />
439
+ </Viewer>
440
+ )
441
+ }
442
+ ```
443
+
444
+ Each `ExplodeGroup` contains a unique `id`, `regionIdxs`, an `offset: { x, y, z }` in
445
+ millimetres in the source geometry's frame, and a numeric RGB `color`. IDs belong to the
446
+ application and need not be feature tags. Groups may contain any region set, and different
447
+ groups can share the same surfaces without being merged. Region IDs must come from the same
448
+ model and mesh. Source geometry must be non-indexed and match `model.mesh.triangleCount`.
449
+ An empty `groups` array draws nothing. Invalid IDs, regions, or offsets throw before allocation.
450
+
451
+ `explode.amount` interpolates all supplied offsets from zero to their full translations;
452
+ values outside `[0, 1]` are clamped and nonfinite amounts use zero. The caller owns animation,
453
+ timing, and reduced-motion preferences. Pass a number for ordinary controlled React updates.
454
+ For frame-by-frame animation, create a stable controller with `createExplodeAmount(initial)`
455
+ and pass it as `explode.amount`:
456
+
457
+ ```tsx
458
+ const [amount] = useState(() => createExplodeAmount(0))
459
+ const explode = useMemo(() => ({ groups, amount }), [groups, amount])
460
+ // From the application's animation loop, slider, or animation library:
461
+ amount.set(0.5)
462
+ ```
463
+
464
+ The controller clamps its value to `[0, 1]`, exposes it as `amount.value`, and notifies
465
+ subscribers only when it changes. `ExplodedPart` subscribes and requests a frame automatically,
466
+ including on a demand-driven canvas, without React updates on every frame. Subscriptions are
467
+ removed on unmount or when the controller changes; one controller can drive multiple viewers.
468
+ The application owns scheduling and cancelling its animation loop. The renderer never moves
469
+ the camera. Fit and Reset remain explicit.
470
+
471
+ Keep the `groups` array stable across amount, selection, and color changes. An optional
472
+ `explode.colors: ReadonlyMap<string, number>` changes colors by group ID without rebuilding
473
+ the layout. Optional `explode.regionColors: ExplodeRegionColors` (a
474
+ `ReadonlyMap<string, ReadonlyMap<number, number>>`) sets colors by group ID and source region index, overriding that group's color. The same
475
+ source region may have different colors in different groups. Regions without an override use
476
+ `explode.colors` or the group's own color; selection and hover tint all of a group's surfaces.
477
+ Replace color maps when changing paint. Unknown group/region keys are ignored. Color changes
478
+ retain geometry buffers, pick identities, and shader programs; they do not require one group
479
+ per region. `visible={false}` retains its buffers while excluding it from rendering, picking,
480
+ and framing. Optional `onReady` runs after warming shaders and buffers for a layout; an app can
481
+ keep it hidden or disable its play control until then. Changing the source or group array
482
+ rebuilds the owned buffers; unmounting releases them without disposing the source geometry.
483
+ Batching uses per-instance colors where Three.js provides them and shared vertex colors on
484
+ earlier supported Three.js releases.
485
+
486
+ Pass `theme?: Partial<ViewerTheme>` to match the surrounding application. `part` colors
487
+ the source ghost, `partEmissive` sets emissive lighting, and `edge`/`edgeOpacity` control
488
+ the outlines (the ghost outline fades further with the explosion). `highlight` tints the
489
+ selected piece, `picked` colors its source surfaces, and `hover` tints the hovered piece. Group colors and
490
+ `explode.colors` remain application-owned. Omit the theme to retain the default explode
491
+ appearance. Theme changes update materials and colors in place without rebuilding geometry,
492
+ changing selection, warming shaders again, or moving the camera.
493
+
494
+ The source part is drawn as an unpickable ghost during explosion. Selecting a group highlights
495
+ its original surfaces and draws one connector. `selected` and `hovered` also accept arrays of
496
+ group IDs, so an application can interact with several render chunks as one logical piece.
497
+ Selection highlights all their source surfaces; one connector joins their combined source and
498
+ translated bounds. `onPick` and `onHover` return an `ExplodePick`:
499
+ the usual source `region` and `triangleIndex`, the application's `explodeGroupId`, the displayed
500
+ world-space `point`/`normal`, and `modelPoint` in the original geometry's frame. Region owners
501
+ and the default ranked feature remain available as `PartPick` fields; applications can use
502
+ the group ID to supply their own metadata. Pass `activeDirection`, as on `<PartMesh>`, to rank
503
+ those owners for one machining direction. Hidden groups and source overlays do not accept picks.
504
+
505
+ At stationary zero, the example gives rendering and picking back to its ordinary `PartMesh`.
506
+ That assembled/exploded mode switch and its toolbar are application UI. The example's candidate
507
+ grouping, colors, translation spacing, and rendering-budget policy stay in
508
+ `examples/react-viewer/src/explode/` and are not exported by the package.
509
+
388
510
  ### `<ViewCube>`, `<Grid>`, and `<Axes>`
389
511
 
390
512
  `ViewCube` defaults to `alignment="top-right"` and `margin={[80, 80]}` (pixels).
@@ -468,7 +590,7 @@ With no children, `ViewerToolbar` renders configured controls in standard order.
468
590
  For custom composition use `ViewerToolbar.Controls`, `ViewerToolbar.Divider`, and
469
591
  its `*Button` components. A button without a matching provider control throws.
470
592
  The supported action names are `stock`, `axes`, `grid`, `banana`, `tools`,
471
- `directions`, `dfm`, `sharpCorners`, `hover`, `focus`, `wireframe`, `section`,
593
+ `directions`, `dfm`, `sharpCorners`, `threads`, `hover`, `focus`, `wireframe`, `section`,
472
594
  `measure`, `fit`, `reset`, and `top`; button names capitalize the action, such as
473
595
  `ViewerToolbar.SharpCornersButton`. `useViewerToolbar()` reads those controls.
474
596
 
@@ -670,6 +792,19 @@ axis legs also have `data-axis`, and finished labels have `data-measurement-id`.
670
792
  millimetres; the `format(mm)` callback changes their displayed string, not geometry
671
793
  units or angles (which remain degrees).
672
794
 
795
+ ### Showing threads
796
+
797
+ The Engine cuts a modeled internal thread out of the mesh, so a tapped hole arrives as a
798
+ plain bore. Pass `threads={{ threads: marks }}` to draw each thread back in as shading
799
+ over its hole's cylinders, at its pitch and hand. Build a mark from a hole's Engine
800
+ datasheet with `threadMark(feature, datasheet)` from `@toolpath/viewer/engine`; it is
801
+ `null` unless the datasheet carries a thread, so only threads the Engine reports are
802
+ drawn. A `ThreadMark` is `{ regions, pitch, majorDiameter }`: `pitch` is in mm and
803
+ negative for a left-hand thread, and cylinders wider than `majorDiameter` (a counterbore)
804
+ stay plain. The axis is fitted from the mesh, so marks need no coordinates. The datasheet
805
+ gives no thread length, so a tap-drill section of the same bore below the thread is
806
+ drawn threaded too.
807
+
673
808
  ### Showing sharp corners and tool marks
674
809
 
675
810
  DFM decisions belong to the application. Pass flagged feature tags as
@@ -719,9 +854,9 @@ Part keys include `part`, `partEmissive`, `hover`, `highlight`, `picked`, `edge`
719
854
  use `measure`/`measureSnap`; cube keys are `cube`, `cubeEdge`, and `cubeLabel`.
720
855
  The canvas background comes from the wrapper/parent, independently of those colors.
721
856
 
722
- Stable DOM classes include `viewer-root`, `viewer-canvas-container`,
723
- `viewer-canvas-frame`, `viewer-canvas`, `viewer-hover-card`, `viewer-toolbar-stack`,
724
- `viewer-toolbar`, `viewer-toolbar-button`, `viewer-toolbar-icon`,
857
+ Stable DOM classes include `viewer-root`, `viewer-webgl-unavailable`,
858
+ `viewer-canvas-container`, `viewer-canvas-frame`, `viewer-canvas`, `viewer-hover-card`,
859
+ `viewer-toolbar-stack`, `viewer-toolbar`, `viewer-toolbar-button`, `viewer-toolbar-icon`,
725
860
  `viewer-toolbar-tooltip`, and `viewer-toolbar-divider`. Caller `className` values
726
861
  append to the component's hook. Corresponding `data-viewer-*` attributes identify
727
862
  the DOM boundaries; `data-viewer-toolbar-action` contains the control's action name.