@toolpath/viewer 1.4.0 → 1.5.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.
@@ -144,6 +144,12 @@ interface PartModel {
144
144
  readonly warnings: readonly string[];
145
145
  }
146
146
 
147
+ /** Enables selection-driven X-ray rendering on a part. */
148
+ interface FocusOptions {
149
+ /** Opacity for regions outside the current selection, from 0 through 1. */
150
+ readonly opacity?: number;
151
+ }
152
+
147
153
  /**
148
154
  * Viewer colors.
149
155
  *
@@ -184,7 +190,8 @@ interface ViewerTheme {
184
190
  /** The cutting plane itself — the translucent sheet `<SectionTool>` draws, its outline, and the surface preview. */
185
191
  readonly sectionOutline: number;
186
192
  /**
187
- * The arrow that drags the cut, and the shell that outlines it. Hovered it
193
+ * The arrow that drags the cut when overridden. When omitted from a theme
194
+ * override, the handle uses the cutting plane's direction colour. Hovered it
188
195
  * takes {@link ViewerTheme.hover}, like every other control here.
189
196
  */
190
197
  readonly sectionHandle: number;
@@ -243,6 +250,7 @@ declare function directionColor(index: number): number;
243
250
 
244
251
  /** The vertex attribute carrying each vertex's column in the state texture. */
245
252
  declare const REGION_ATTRIBUTE = "aRegion";
253
+ type PartDisplay = 'solid' | 'wireframe';
246
254
  /**
247
255
  * A part on screen: one mesh, one draw call, one material.
248
256
  *
@@ -271,6 +279,10 @@ interface PartObject {
271
279
  /** Paints every region the feature explicitly owns. */
272
280
  paintFeature(tag: FeatureTag, color: number, weight: number): void;
273
281
  clearPaint(): void;
282
+ /** Makes selected features solid and the rest of the part translucent. */
283
+ setFocus(selection: readonly FeatureTag[], focus?: FocusOptions): void;
284
+ /** The current opacity of one region, or `null` if it does not exist. */
285
+ regionOpacity(region: number): number | null;
274
286
  /** A feature's bounds in part space, for framing. `null` if it has none. */
275
287
  boxForFeature(tag: FeatureTag): Box3 | null;
276
288
  /**
@@ -280,6 +292,8 @@ interface PartObject {
280
292
  */
281
293
  setClippingPlanes(planes: readonly Plane[] | null): void;
282
294
  setTheme(theme: ViewerTheme): void;
295
+ /** Semantic edges only in wireframe; painted faces remain visible for interaction. */
296
+ setDisplay(display: PartDisplay, showEdges?: boolean): void;
283
297
  dispose(): void;
284
298
  }
285
299
  interface RegionPaint {
@@ -422,6 +436,8 @@ declare const SECTION_RENDER_ORDER: {
422
436
  };
423
437
  /** The handle's length on screen, in CSS pixels, whatever the zoom. */
424
438
  declare const HANDLE_PIXELS = 78;
439
+ /** The viewer-space span of the visible section-plane frame, relative to the part diagonal. */
440
+ declare const SECTION_GIZMO_FRAME_SCALE = 1.1;
425
441
  /**
426
442
  * The hatch on the cap: stripe pitch and line thickness in CSS pixels, and the
427
443
  * outline's width. Screen-space, so the cut reads the same at any zoom.
@@ -463,6 +479,12 @@ interface SectionState {
463
479
  readonly constant: number;
464
480
  readonly plane: SectionPlacement | null;
465
481
  readonly depth: number | null;
482
+ /**
483
+ * Distance swept from the edge of the part into its projected bounds, in
484
+ * model units. A surface-anchored cut also reports `depth`, which is its
485
+ * more useful physical datum.
486
+ */
487
+ readonly cutDistance?: number;
466
488
  /**
467
489
  * How far the cut can travel from its anchor, in model units, or `null` for a
468
490
  * sweep — which is measured as a fraction of the part rather than a distance.
@@ -484,6 +506,13 @@ declare const DEFAULT_SECTION_NORMAL: Vec3;
484
506
  * own state never has to special-case the absence: `enabled` says it.
485
507
  */
486
508
  declare const DISABLED_SECTION: SectionState;
509
+ /**
510
+ * A concise physical measurement for a host application's section control.
511
+ *
512
+ * A cut placed from a surface is measured from that surface. A free sweep is
513
+ * measured from the edge of the part along the cut normal.
514
+ */
515
+ declare function sectionMeasurement(state: SectionState): string;
487
516
  /**
488
517
  * The options that would resolve to `state` again.
489
518
  *
@@ -526,9 +555,9 @@ interface SurfaceHit {
526
555
  *
527
556
  * "The part" is whatever in the scene is a visible mesh outside an overlay —
528
557
  * every overlay here marks its outermost group with `EXCLUDE_FROM_FRAME`, the
529
- * same flag that keeps it out of the camera's framing, and the ones that are
530
- * not clickable turn their own raycast off besides. What is left is the
531
- * geometry the consumer put in.
558
+ * same flag that keeps it out of the camera's framing. Stock has its own flag:
559
+ * it belongs in Fit, but not in section or measurement picks. Non-clickable
560
+ * overlays also turn their own raycast off.
532
561
  *
533
562
  * A surface a section cut has clipped away is skipped too. three's raycaster
534
563
  * knows nothing about clipping planes, so without this a ray through the open
@@ -572,18 +601,6 @@ interface SectionPlacement {
572
601
  readonly label?: string;
573
602
  }
574
603
  declare const PICKED_SURFACE_LABEL = "Part surface";
575
- /**
576
- * The range of plane constants that sweeps a box along `normal`.
577
- *
578
- * Derived from the eight corners rather than from a single axis extent, which is
579
- * what makes an arbitrary normal work: a tilted plane leaves the box through a
580
- * corner, and an axis-aligned approximation either stops short of cutting the
581
- * part or sweeps a long way through empty space before reaching it.
582
- *
583
- * `Plane` keeps the half-space where `normal · p + constant > 0`, so a *larger*
584
- * constant clips less. `min` and `max` are named for the constant, not for how
585
- * much they remove.
586
- */
587
604
  declare function sectionBounds(box: Box3, normal: Vec3): SectionBounds;
588
605
  /** The plane constant at `t`, from 0 (uncut) to 1 (fully cut away). */
589
606
  declare function sectionConstant(bounds: SectionBounds, t: number): number;
@@ -657,6 +674,11 @@ interface PickModifiers {
657
674
  readonly secondary: boolean;
658
675
  }
659
676
  declare const NO_MODIFIERS: PickModifiers;
677
+ /** A browser-viewport point, for placing an application-owned hover card. */
678
+ interface PointerLocation {
679
+ readonly clientX: number;
680
+ readonly clientY: number;
681
+ }
660
682
  /**
661
683
  * A pointer event on the part, resolved to the face it landed on and the
662
684
  * features that own it.
@@ -678,6 +700,8 @@ interface PartPick {
678
700
  readonly point: readonly [number, number, number];
679
701
  /** The surface's outward normal in world space — the plane under the cursor. */
680
702
  readonly normal: readonly [number, number, number];
703
+ /** Browser coordinates when the pick came from `<PartMesh>`. */
704
+ readonly pointer?: PointerLocation;
681
705
  readonly modifiers: PickModifiers;
682
706
  /**
683
707
  * Whether this click completed a double click on the part.
@@ -703,6 +727,7 @@ interface BuildPickInput {
703
727
  readonly triangleIndex: number;
704
728
  readonly point: readonly [number, number, number];
705
729
  readonly normal: readonly [number, number, number];
730
+ readonly pointer?: PointerLocation;
706
731
  readonly modifiers?: PickModifiers;
707
732
  /**
708
733
  * The machining direction the pick is scoped to, as an index into
@@ -740,6 +765,11 @@ interface PartMeshProps {
740
765
  * one-to-many and no scoping rule fixes that.
741
766
  */
742
767
  selection?: readonly FeatureTag[];
768
+ /**
769
+ * Makes selected feature regions solid and the rest of the part translucent.
770
+ * Omit it for the normal solid view. The application still owns selection.
771
+ */
772
+ focus?: FocusOptions;
743
773
  /**
744
774
  * Every feature a click could have meant, painted faintly in each one's own
745
775
  * direction colour, under the selection.
@@ -761,6 +791,11 @@ interface PartMeshProps {
761
791
  * and needs no prop.
762
792
  */
763
793
  hoveredFeatureIds?: readonly FeatureTag[];
794
+ /**
795
+ * Whether moving across the part paints and reports a hovered face. Turn it
796
+ * off for an application-level "feature hover" control; picks still work.
797
+ */
798
+ hover?: boolean;
764
799
  /**
765
800
  * Scopes a pick to one machining direction, as an index into the model's
766
801
  * `candidateDirections`. A face that direction cannot reach then picks to
@@ -814,6 +849,8 @@ interface PartMeshProps {
814
849
  onPick?: (pick: PartPick) => void;
815
850
  theme?: Partial<ViewerTheme>;
816
851
  showEdges?: boolean;
852
+ /** Wireframe shows region boundaries and any hovered/painted faces, with no triangle diagonals. */
853
+ display?: PartDisplay;
817
854
  }
818
855
  /**
819
856
  * The part, as one mesh with one material.
@@ -825,7 +862,7 @@ interface PartMeshProps {
825
862
  * React render at all: the hovered face lives in a ref, and routing it through
826
863
  * state is what this rewrite exists to stop doing.
827
864
  */
828
- declare const PartMesh: ({ model, geometry, selection, candidates, highlights, regionHighlights, pickedRegions, hoveredFeatureIds, activeDirection, section, onSectionChange, onAdjacency, focusFeature, onHover, onPick, theme, showEdges, }: PartMeshProps) => react.JSX.Element;
865
+ declare const PartMesh: ({ model, geometry, selection, focus, candidates, highlights, regionHighlights, pickedRegions, hoveredFeatureIds, hover, activeDirection, section, onSectionChange, onAdjacency, focusFeature, onHover, onPick, theme, showEdges, display, }: PartMeshProps) => react.JSX.Element;
829
866
 
830
867
  interface EnginePartProps extends Omit<PartMeshProps, 'model' | 'geometry'> {
831
868
  /**
@@ -900,4 +937,4 @@ declare function normalizePartReport(report: unknown): PartModel;
900
937
  */
901
938
  declare function assertSupportedKernelVersion(kernelVersion: string): void;
902
939
 
903
- export { type SurfaceHit as $, AXES_PLANE_OFFSET as A, type BuildPickInput as B, CANDIDATE_WEIGHT as C, DEFAULT_SECTION_NORMAL as D, EnginePart as E, type FeatureTag as F, type PartObject as G, HANDLE_PIXELS as H, type PartPick as I, type PickModifiers as J, type KnownFeatureType as K, REGION_ATTRIBUTE as L, MIN_KERNEL_VERSION as M, NO_MODIFIERS as N, OUTLINE_SCALE as O, type PartModel as P, type RegionHighlight as Q, type RegionIndex as R, type SectionOptions as S, type TriangleRange as T, type RegionPaint as U, type Vec3 as V, SECTION_RENDER_ORDER as W, type SectionAnchor as X, type SectionBounds as Y, type SectionPlacement as Z, type ShapeKind as _, type PartMeshRefs as a, applyHighlightLayers as a0, axesPlanes as a1, buildPick as a2, buildRegionAttribute as a3, buildRegionTexels as a4, createPart as a5, directionColor as a6, dragPlane as a7, focusForPick as a8, hitUnderRay as a9, pickedStartDepth as aa, resolveTheme as ab, sectionAnchor as ac, sectionBounds as ad, sectionConstant as ae, sectionDepth as af, sectionDepthConstant as ag, sectionDepthRange as ah, sectionFromPick as ai, sectionOffset as aj, sectionOptionsFromState as ak, sectionPlane as al, surfaceUnderRay as am, themesEqual as an, viewDirection as ao, type EnginePartProps as b, assertSupportedKernelVersion as c, type ViewerTheme as d, type SectionState as e, type PartModelRegion as f, type PartModelFeature as g, type FeatureType as h, AXES_PLANE_SCALE as i, type AxesPlane as j, CAP_HATCH as k, DEFAULT_THEME as l, DIRECTION_COLORS as m, normalizePartReport as n, DISABLED_SECTION 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, type KnownShapeKind as v, PICKED_SURFACE_LABEL as w, PREVIEW_SCALE as x, PartMesh as y, type PartMeshProps as z };
940
+ export { type SectionAnchor as $, AXES_PLANE_OFFSET as A, type BuildPickInput as B, CANDIDATE_WEIGHT as C, DEFAULT_SECTION_NORMAL as D, EnginePart as E, type FeatureTag as F, PREVIEW_SCALE as G, HANDLE_PIXELS as H, type PartDisplay as I, PartMesh as J, type KnownFeatureType as K, type PartMeshProps as L, MIN_KERNEL_VERSION as M, NO_MODIFIERS as N, OUTLINE_SCALE as O, type PartModel as P, type PartObject as Q, type RegionHighlight as R, type SectionOptions as S, type TriangleRange as T, type PickModifiers as U, type Vec3 as V, type PointerLocation as W, REGION_ATTRIBUTE as X, type RegionPaint as Y, SECTION_GIZMO_FRAME_SCALE as Z, SECTION_RENDER_ORDER as _, type PartMeshRefs as a, type SectionBounds as a0, type SectionPlacement as a1, type ShapeKind as a2, type SurfaceHit as a3, applyHighlightLayers as a4, axesPlanes as a5, buildPick as a6, buildRegionAttribute as a7, buildRegionTexels as a8, createPart as a9, directionColor as aa, dragPlane as ab, focusForPick as ac, hitUnderRay as ad, pickedStartDepth as ae, resolveTheme as af, sectionAnchor as ag, sectionBounds as ah, sectionConstant as ai, sectionDepth as aj, sectionDepthConstant as ak, sectionDepthRange as al, sectionFromPick as am, sectionMeasurement as an, sectionOffset as ao, sectionOptionsFromState as ap, sectionPlane as aq, surfaceUnderRay as ar, themesEqual as as, viewDirection as at, type EnginePartProps as b, assertSupportedKernelVersion as c, type PartPick as d, type ViewerTheme as e, type SectionState as f, type PartModelRegion as g, type RegionIndex as h, type PartModelFeature as i, type FeatureType as j, AXES_PLANE_SCALE as k, type AxesPlane as l, CAP_HATCH as m, normalizePartReport as n, DEFAULT_THEME as o, DIRECTION_COLORS as p, DISABLED_SECTION as q, type FeatureHighlight as r, smoothRegionNormals as s, type FocusOptions as t, HIGHLIGHT_COLORS as u, HIGHLIGHT_WEIGHT as v, HOVER_WEIGHT as w, type HighlightLayers as x, type KnownShapeKind as y, PICKED_SURFACE_LABEL as z };
@@ -0,0 +1,108 @@
1
+ .viewer-toolbar-stack {
2
+ position: absolute;
3
+ bottom: 1.25rem;
4
+ left: 0;
5
+ width: 100%;
6
+ padding: 0 0.75rem;
7
+ z-index: 2;
8
+ pointer-events: none;
9
+ display: flex;
10
+ flex-direction: column;
11
+ align-items: center;
12
+ gap: 0.6rem;
13
+ }
14
+ .viewer-toolbar,
15
+ .viewer-tool-options {
16
+ pointer-events: auto;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ flex-wrap: wrap;
21
+ gap: 0.25rem;
22
+ max-width: 100%;
23
+ padding: 0.4rem;
24
+ border: 1px solid #52607880;
25
+ border-radius: 0.6rem;
26
+ background: #202737df;
27
+ backdrop-filter: blur(12px);
28
+ box-shadow: 0 3px 14px #0003;
29
+ }
30
+ .viewer-toolbar-stack button,
31
+ .viewer-toolbar-button {
32
+ border: 1px solid #526078;
33
+ border-radius: 0.4rem;
34
+ background: #202737;
35
+ color: #eef0f6;
36
+ cursor: pointer;
37
+ padding: 0.45rem 0.65rem;
38
+ }
39
+ .viewer-toolbar button,
40
+ .viewer-toolbar-button {
41
+ position: relative;
42
+ display: grid;
43
+ place-items: center;
44
+ width: 2.2rem;
45
+ height: 2.2rem;
46
+ padding: 0.4rem;
47
+ border-color: transparent;
48
+ background: transparent;
49
+ }
50
+ .viewer-toolbar-icon {
51
+ width: 1.3rem;
52
+ height: 1.3rem;
53
+ }
54
+ .viewer-toolbar-divider {
55
+ width: 1px;
56
+ height: 1.4rem;
57
+ margin: 0 0.2rem;
58
+ background: #526078;
59
+ }
60
+ .viewer-toolbar-tooltip {
61
+ display: none;
62
+ position: absolute;
63
+ bottom: calc(100% + 0.75rem);
64
+ left: 50%;
65
+ transform: translateX(-50%);
66
+ white-space: nowrap;
67
+ background: #101522;
68
+ padding: 0.4rem 0.6rem;
69
+ border-radius: 0.3rem;
70
+ pointer-events: none;
71
+ font-size: 0.75rem;
72
+ z-index: 3;
73
+ }
74
+ .viewer-toolbar-button:hover .viewer-toolbar-tooltip,
75
+ .viewer-toolbar-button:focus-visible .viewer-toolbar-tooltip {
76
+ display: block;
77
+ }
78
+ .viewer-toolbar-button:focus {
79
+ outline: none;
80
+ }
81
+ .viewer-toolbar-button:focus-visible {
82
+ outline: 2px solid #83a9ff;
83
+ outline-offset: 2px;
84
+ }
85
+ .viewer-toolbar-button:hover {
86
+ background: #303b51;
87
+ }
88
+ .viewer-toolbar-button[aria-pressed='true'] {
89
+ border-color: #83a9ff;
90
+ background: #2b3f66;
91
+ }
92
+ .viewer-toolbar label {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 0.4rem;
96
+ color: #b9c0d1;
97
+ font-size: 0.85rem;
98
+ }
99
+ .viewer-tool-options label {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 0.4rem;
103
+ }
104
+ .viewer-hint {
105
+ align-self: center;
106
+ color: #b9c0d1;
107
+ font-size: 0.85rem;
108
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolpath/viewer",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "React Three Fiber viewer for Toolpath Engine part reports",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -30,7 +30,8 @@
30
30
  "./engine": {
31
31
  "types": "./dist/engine/index.d.ts",
32
32
  "import": "./dist/engine/index.js"
33
- }
33
+ },
34
+ "./toolbar.css": "./dist/toolbar.css"
34
35
  },
35
36
  "files": [
36
37
  "dist",
@@ -63,8 +64,8 @@
63
64
  "camera-controls": "3.1.0"
64
65
  },
65
66
  "scripts": {
66
- "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",
67
- "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",
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 && node scripts/copy-assets.mjs",
68
+ "build:watch": "node scripts/copy-assets.mjs && 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",
68
69
  "check-types": "tsc --noEmit",
69
70
  "test": "vitest run"
70
71
  }