@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.
package/README.md CHANGED
@@ -76,6 +76,7 @@ What each wrapper is for:
76
76
  ├─ <EnginePart> validates the report, loads the mesh, then renders <PartMesh>
77
77
  │ └─ <PartMesh> draws the part and handles hover, click, colours, section cuts
78
78
  ├─ <DirectionArrows> arrows for the directions the part can be machined from
79
+ ├─ <Stock> <BoxStock> translucent stock, included when fitting the camera
79
80
  ├─ <SectionTool> optional: click a face or a plane to cut the part open
80
81
  ├─ <MeasureTool> optional: click two points for a distance, three for an angle
81
82
  ├─ <Grid> <Axes> reference geometry, sized to the part
@@ -95,6 +96,16 @@ What each wrapper is for:
95
96
  4. **You own the state.** The viewer tells you what was clicked (`onPick`), and you tell it what to
96
97
  highlight (`selection`, `highlights`, …). It never changes your selection by itself.
97
98
 
99
+ ### Focus the current selection
100
+
101
+ Pass `focus` to make selected features solid while the remainder of the part becomes translucent.
102
+ The default outside opacity is 15%; set `opacity` for a different X-ray strength. With no selected
103
+ features, the part stays fully opaque.
104
+
105
+ ```tsx
106
+ <EnginePart report={report} selection={selection} focus={{ opacity: 0.12 }} />
107
+ ```
108
+
98
109
  Parts are in **millimetres** with **Z pointing up**. The camera uses the same convention.
99
110
 
100
111
  ### A click usually matches several features
@@ -103,23 +114,221 @@ The same face is usually owned by **5–8 features at once**, even on a plain cu
103
114
  `face` when cut from one direction, a `wall` when cut from another, and part of every `profile`
104
115
  around it. So a click gives you every match, and you decide which to use:
105
116
 
106
- | Field | What it contains |
107
- | ---------------- | ------------------------------------------------------------------- |
108
- | `pick.best` | The most likely feature, or `null` |
109
- | `pick.ranked` | Every matching feature, most likely first |
110
- | `pick.owners` | Every matching feature, in report order |
111
- | `pick.region` | The index of the face that was clicked |
112
- | `pick.point` | Where the click hit, as `[x, y, z]` |
113
- | `pick.normal` | The direction the clicked surface faces, as `[x, y, z]` |
114
- | `pick.modifiers` | `{ alt, ctrl, meta, shift, secondary }`: keys held, and right-click |
115
- | `pick.doubled` | `true` if this click was the second half of a double-click |
117
+ | Field | What it contains |
118
+ | ---------------- | --------------------------------------------------------------------- |
119
+ | `pick.best` | The most likely feature, or `null` |
120
+ | `pick.ranked` | Every matching feature, most likely first |
121
+ | `pick.owners` | Every matching feature, in report order |
122
+ | `pick.region` | The index of the face that was clicked |
123
+ | `pick.point` | Where the click hit, as `[x, y, z]` |
124
+ | `pick.normal` | The direction the clicked surface faces, as `[x, y, z]` |
125
+ | `pick.pointer` | Browser coordinates for an application-owned hover card, when present |
126
+ | `pick.modifiers` | `{ alt, ctrl, meta, shift, secondary }`: keys held, and right-click |
127
+ | `pick.doubled` | `true` if this click was the second half of a double-click |
116
128
 
117
129
  The ranking puts specific features first: holes, then pockets and bosses, then chamfers and
118
130
  fillets, then walls and faces, then profiles. Among features of the same kind, the one whose
119
131
  machining direction points most toward the camera wins.
120
132
 
133
+ `onHover` receives the same `PartPick` shape. Use the optional `pointer` location yourself, or
134
+ wrap application content in `<HoverCard pick={hoverPick}>`; it follows the cursor while that
135
+ region remains hovered. The viewer intentionally leaves card content and actions to the application.
136
+ Hover feedback is on by default; pass `hover={false}` to disable both face feedback and `onHover`
137
+ callbacks while preserving clicks. That makes an application toolbar's feature-hover toggle
138
+ unambiguous.
139
+
140
+ The normalized model contains feature identity, type, directions, face shape, and analytic area.
141
+ An Engine DFM card can join `pick.best` to the part's detailed feature data and render its own
142
+ depth, clearance-diameter, or L/D rows inside `HoverCard`; setup labels remain application or plan
143
+ context rather than a fact of the part surface.
144
+
121
145
  ## Components
122
146
 
147
+ ### Stock and display controls
148
+
149
+ Use `<BoxStock>` for an axis-aligned blank around a part, or `<Stock geometry={stockGeometry} />`
150
+ for an actual stock mesh, including cylindrical or irregular blanks. Stock and part coordinates
151
+ must use the same millimetre, Z-up frame. Both components include stock in Fit and Reset while
152
+ keeping section tools, measurements, the grid, and direction arrows sized to the finished part.
153
+ Stock does not intercept clicks or get clipped by the part's section plane.
154
+
155
+ ```tsx
156
+ import { useMemo, useRef, useState } from 'react'
157
+ import {
158
+ Axes,
159
+ BoxStock,
160
+ DirectionArrows,
161
+ PartMesh,
162
+ Viewer,
163
+ directionHighlights,
164
+ } from '@toolpath/viewer'
165
+ import type { PartModel, ViewerHandle } from '@toolpath/viewer'
166
+ import type { BufferGeometry } from 'three'
167
+
168
+ export function StockPreview({ model, geometry }: { model: PartModel; geometry: BufferGeometry }) {
169
+ const viewer = useRef<ViewerHandle>(null)
170
+ const [stock, setStock] = useState(false)
171
+ const [axes, setAxes] = useState(true)
172
+ const [directions, setDirections] = useState(false)
173
+ const [direction, setDirection] = useState<number | null>(null)
174
+ const [wireframe, setWireframe] = useState(false)
175
+ const colors = useMemo(
176
+ () => (directions ? directionHighlights(model, direction) : []),
177
+ [model, direction, directions],
178
+ )
179
+
180
+ return (
181
+ <>
182
+ <Viewer ref={viewer} style={{ height: 500 }}>
183
+ <PartMesh
184
+ model={model}
185
+ geometry={geometry}
186
+ display={wireframe ? 'wireframe' : 'solid'}
187
+ regionHighlights={colors}
188
+ activeDirection={directions ? direction : null}
189
+ />
190
+ {stock && <BoxStock partGeometry={geometry} allowance={{ wall: 3, floor: 3 }} />}
191
+ {axes && <Axes />}
192
+ <DirectionArrows
193
+ directions={model.candidateDirections}
194
+ visible={directions}
195
+ shownDirection={direction}
196
+ onPickDirection={(index) => setDirection((held) => (held === index ? null : index))}
197
+ />
198
+ </Viewer>
199
+ <button aria-pressed={stock} onClick={() => setStock(!stock)}>
200
+ Stock
201
+ </button>
202
+ <button aria-pressed={axes} onClick={() => setAxes(!axes)}>
203
+ Axes
204
+ </button>
205
+ <button aria-pressed={directions} onClick={() => setDirections(!directions)}>
206
+ By direction
207
+ </button>
208
+ <button aria-pressed={wireframe} onClick={() => setWireframe(!wireframe)}>
209
+ Wireframe
210
+ </button>
211
+ <button onClick={() => viewer.current?.fit()}>Fit</button>
212
+ </>
213
+ )
214
+ }
215
+ ```
216
+
217
+ `BoxStock.allowance` is padding **per side**, in millimetres. The preferred form is
218
+ `{ wall, floor }`: wall stock expands X/Y and floor stock expands Z, matching the wall/floor
219
+ roughing-stock distinction used by machining settings. A number or `{ x, y, z }` remains accepted
220
+ for uniform or axis-specific padding. It defaults to zero. `offset` translates the stock from the
221
+ part's bounding-box centre. `boxStockBounds(geometry, allowance, offset)` returns the same `Box3`
222
+ for displaying dimensions.
223
+
224
+ For fixed-box stock, pass `dimensions={{ x, y, z }}` instead. `position` accepts
225
+ `model_centered`, `offset_from_top`, or `offset_from_bottom`, and `positionOffset` is the distance
226
+ from the selected top or bottom bound. All fixed-box values use millimetres and the part's Z-up
227
+ coordinate frame.
228
+ Negative/non-finite allowances, non-finite coordinates, and empty or non-positive stock dimensions
229
+ throw `RangeError`. The 3 mm allowance above is an example, not an automatic stock recommendation.
230
+
231
+ `Stock` accepts `color`, `opacity` (default `0.2`), `edgeColor`, `edgeOpacity`, and `showEdges`.
232
+ `BoxStock` accepts the same appearance props. Caller-provided geometry is never disposed; the
233
+ components dispose their own materials and outlines. Conditionally mount stock to toggle it.
234
+ Toggling stock preserves the camera; Fit/Reset then frames the visible stock together with the part.
235
+
236
+ `directionHighlights(model, activeDirection?)` returns region colors in the same palette as
237
+ `DirectionArrows`. For a face with several owners, the most specific feature wins, followed by
238
+ candidate-direction order and feature tag. Unmatched directions are left unpainted. Hover and
239
+ selection still paint over this wash. Scoping by direction filters ownership; it does not hide
240
+ the rest of the model or claim that an unpainted face cannot be manufactured.
241
+
242
+ Wireframe keeps the existing semantic edges, including rear edges, and keeps faces available for
243
+ picking, measuring, and section placement. Highlighted faces and section caps remain visible.
244
+ `showEdges` controls solid-mode outlines; wireframe always shows them. The example's bottom toolbar
245
+ makes wireframe and direction coloring mutually exclusive and includes a direction legend and
246
+ contextual Section/Measure controls.
247
+
248
+ ### `<ViewerToolbar>`
249
+
250
+ `ViewerToolbar` is a composable, viewer-scoped toolbar for camera, display, section, and measurement
251
+ controls. Import its stylesheet alongside your application stylesheet:
252
+
253
+ ```tsx
254
+ import '@toolpath/viewer/toolbar.css'
255
+ import { ViewerToolbar, ViewerToolbarProvider } from '@toolpath/viewer'
256
+ ```
257
+
258
+ Give `ViewerToolbarProvider` the current values and callbacks from the host app, then place the
259
+ toolbar and viewer inside it. The toolbar owns no application state or behavior: it only reads the
260
+ provider. Omit `children` for every configured control in the standard order, or compose the
261
+ individual buttons to use only the controls and order your application needs.
262
+
263
+ ```tsx
264
+ <ViewerToolbarProvider
265
+ controls={{
266
+ stock: { pressed: showStock, onClick: () => setShowStock((shown) => !shown) },
267
+ measure: { pressed: measuring, onClick: toggleMeasuring },
268
+ fit: { onClick: () => viewer.current?.fit() },
269
+ }}
270
+ >
271
+ <ViewerToolbar>
272
+ <ViewerToolbar.Controls>
273
+ <ViewerToolbar.MeasureButton />
274
+ <ViewerToolbar.Divider />
275
+ <ViewerToolbar.StockButton />
276
+ <ViewerToolbar.FitButton />
277
+ </ViewerToolbar.Controls>
278
+ </ViewerToolbar>
279
+ <Viewer ref={viewer}>…</Viewer>
280
+ </ViewerToolbarProvider>
281
+ ```
282
+
283
+ `ViewerToolbar.Controls`, `Divider`, and the thirteen `*Button` components are available on
284
+ `ViewerToolbar`: `StockButton`, `AxesButton`, `GridButton`, `BananaButton`, `DirectionsButton`,
285
+ `HoverButton`, `FocusButton`, `WireframeButton`, `SectionButton`, `MeasureButton`, `FitButton`,
286
+ `ResetButton`, and `TopButton`. A rendered button requires its matching provider control. For an
287
+ application-specific toolbar component, `useViewerToolbar()` reads the same controls from inside
288
+ the provider.
289
+
290
+ #### Styling hooks
291
+
292
+ Every viewer, hover-card, and toolbar DOM boundary has a stable class name. Your `className` on
293
+ `<Viewer>`, `<ViewerToolbar>`, or `<HoverCard>` is appended to the component's own class; it never
294
+ replaces the hook. The toolbar stylesheet uses the same names, so import it for the default look or
295
+ override any of these selectors in your application stylesheet.
296
+
297
+ | Component | Class | Element |
298
+ | ----------------- | ------------------------- | --------------------------------- |
299
+ | `<Viewer>` | `viewer-root` | Wrapper around the canvas |
300
+ | `<Viewer>` | `viewer-canvas-container` | R3F canvas event container |
301
+ | `<Viewer>` | `viewer-canvas-frame` | R3F's canvas-sizing frame |
302
+ | `<Viewer>` | `viewer-canvas` | The `<canvas>` itself |
303
+ | `<HoverCard>` | `viewer-hover-card` | Cursor-following tooltip shell |
304
+ | `<ViewerToolbar>` | `viewer-toolbar-stack` | Outer toolbar stack |
305
+ | `<ViewerToolbar>` | `viewer-toolbar` | Controls group |
306
+ | `<ViewerToolbar>` | `viewer-toolbar-button` | Each standard control button |
307
+ | `<ViewerToolbar>` | `viewer-toolbar-icon` | SVG within a standard control |
308
+ | `<ViewerToolbar>` | `viewer-toolbar-tooltip` | Label shown on button hover/focus |
309
+ | `<ViewerToolbar>` | `viewer-toolbar-divider` | Separator between control groups |
310
+
311
+ The following `data-*` attributes identify generated elements without depending on labels, which
312
+ can change with state or localization:
313
+
314
+ | Attribute | Element and values |
315
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
316
+ | `data-viewer-root="true"` | `<Viewer>` wrapper |
317
+ | `data-viewer-canvas-container="true"` | R3F canvas event container |
318
+ | `data-viewer-canvas-frame="true"` | R3F canvas-sizing frame |
319
+ | `data-viewer-canvas="true"` | The `<canvas>` itself |
320
+ | `data-viewer-hover-card="true"` | `<HoverCard>` shell |
321
+ | `data-viewer-toolbar="true"` | Toolbar stack |
322
+ | `data-viewer-toolbar-controls="true"` | Controls group |
323
+ | `data-viewer-toolbar-action` | Standard button: `stock`, `axes`, `grid`, `banana`, `directions`, `hover`, `focus`, `wireframe`, `section`, `measure`, `fit`, `reset`, or `top` |
324
+ | `data-viewer-toolbar-icon="true"` | Standard button SVG |
325
+ | `data-viewer-toolbar-tooltip="true"` | Standard button tooltip |
326
+ | `data-viewer-toolbar-divider="true"` | Toolbar separator |
327
+
328
+ Toggle buttons also carry standard `aria-pressed="true"` or `"false"`; use that to style their
329
+ current state. `Fit`, `Reset`, and `Top view` are actions rather than toggles, so they omit the
330
+ attribute.
331
+
123
332
  ### `<Viewer>`
124
333
 
125
334
  The canvas that holds everything. Import it from `@toolpath/viewer`.
@@ -127,7 +336,7 @@ The canvas that holds everything. Import it from `@toolpath/viewer`.
127
336
  | Prop | Default | What it does |
128
337
  | ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- |
129
338
  | `projection` | `'orthographic'` | `'orthographic'` keeps parallel lines parallel. `'perspective'` gives depth, which helps with deep pockets. |
130
- | `controls` | `'toolpath'` | Mouse mapping. See [Mouse controls](#mouse-controls). |
339
+ | `controls` | `'toolpath'` | CAD navigation preset. See [CAD controls](#cad-controls). |
131
340
  | `zoomTo` | `'cursor'` | Zoom toward the pointer, or toward `'centre'` (often easier on a trackpad). |
132
341
  | `freeOrbit` | `true` | Let the view keep rotating past straight-up and straight-down. |
133
342
  | `retargetOnDoubleClick` | `true` | Double-click the part to rotate around that point. |
@@ -142,20 +351,35 @@ Changing `projection` rebuilds the canvas and returns the camera to its starting
142
351
 
143
352
  The canvas is transparent. To set a background colour, style the wrapper or its parent.
144
353
 
145
- #### Mouse controls
354
+ #### CAD controls
355
+
356
+ `controls` changes only the navigation gestures. It does not choose a projection: the viewer stays
357
+ orthographic by default, and `projection="perspective"` remains an independent opt-in.
358
+
359
+ | Value | Label | Rotate | Pan | Zoom |
360
+ | -------------- | ---------- | ------------------------------------- | ----------------------------------------- | ----------------------------------- |
361
+ | `'toolpath'` | Toolpath | Left-drag | Right-drag | Scroll wheel |
362
+ | `'fusion'` | Fusion | Shift + middle-drag or Shift + scroll | Middle-drag, scroll, or two-finger scroll | Trackpad pinch |
363
+ | `'alias'` | Alias | Left-drag | Middle-drag | Scroll wheel |
364
+ | `'inventor'` | Inventor | Shift + middle-drag or Shift + scroll | Middle-drag, scroll, or two-finger scroll | Trackpad pinch |
365
+ | `'solidworks'` | SolidWorks | Middle-drag | Ctrl + middle-drag | Shift + middle-drag or scroll wheel |
366
+ | `'tinkercad'` | Tinkercad | Right-drag | Shift + right-drag | Scroll wheel |
367
+ | `'powermill'` | PowerMill | Middle-drag | Shift + middle-drag | Scroll wheel |
368
+ | `'onshape'` | Onshape | Right-drag | Middle-drag | Scroll wheel |
369
+
370
+ Fusion and Inventor treat a two-finger scroll as pan, Shift + two-finger scroll as orbit, and a
371
+ pinch (a wheel event with Ctrl set by the browser) as zoom. Other schemes use one-finger rotate,
372
+ two-finger pinch-and-pan, and three-finger pan on touch screens.
146
373
 
147
- | Action | `controls="toolpath"` (default) | `controls="fusion"` (like Fusion 360) |
148
- | --------------------- | ------------------------------- | ------------------------------------------ |
149
- | Rotate | Left-drag | Shift + middle-drag, or Shift + scroll |
150
- | Pan | Right-drag or middle-drag | Middle-drag, or scroll / two-finger scroll |
151
- | Zoom | Scroll wheel | Trackpad pinch |
152
- | Rotate around a point | Double-click the part | Double-click the part |
153
- | Fit whole part | Double middle-click | Double middle-click |
374
+ All schemes support double-clicking the part to rotate around that point and double-clicking the
375
+ middle mouse button to fit the whole part. Dragging never selects anything; a click only counts if
376
+ the pointer barely moved.
154
377
 
155
- In `fusion` mode, left-drag doesn't move the camera, and the scroll wheel pans instead of zooming.
156
- On touch screens, one finger rotates and two fingers pinch and pan.
378
+ The package exports `CONTROL_SCHEME_OPTIONS`, `ControlScheme`, and `ControlSchemeOption` for a host
379
+ application's settings UI. It deliberately stores no preferences: pass the selected `controls`,
380
+ `freeOrbit`, and `zoomTo` values to `<Viewer>` from your own state or persistence layer.
157
381
 
158
- Dragging never selects anything. A click only counts if the pointer barely moved.
382
+ `'toolpath'`, `freeOrbit={true}`, and `zoomTo="cursor"` are the defaults.
159
383
 
160
384
  ### `<EnginePart>`
161
385
 
@@ -176,28 +400,30 @@ Draws the part and handles clicks. You only use it directly when you
176
400
 
177
401
  **Colours**
178
402
 
179
- | Prop | Type | What it does |
180
- | ------------------- | ---------------------- | -------------------------------------------------------------- |
181
- | `selection` | `string[]` | Feature tags to highlight as selected (orange). |
182
- | `highlights` | `FeatureHighlight[]` | Your own colour per feature, e.g. difficulty or setup. |
183
- | `regionHighlights` | `RegionHighlight[]` | Your own colour per face. |
184
- | `candidates` | `string[]` | Other possible matches, faintly tinted by machining direction. |
185
- | `pickedRegions` | `number[]` | Faces to mark as just clicked. |
186
- | `hoveredFeatureIds` | `string[]` | Features to show as hovered, e.g. when hovering a list row. |
187
- | `showEdges` | `boolean` (`true`) | Draw outlines between faces. |
188
- | `theme` | `Partial<ViewerTheme>` | Override the part's colours. |
403
+ | Prop | Type | What it does |
404
+ | ------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
405
+ | `selection` | `string[]` | Feature tags to highlight as selected (orange). |
406
+ | `highlights` | `FeatureHighlight[]` | Your own colour per feature, e.g. difficulty or setup. |
407
+ | `regionHighlights` | `RegionHighlight[]` | Your own colour per face. |
408
+ | `candidates` | `string[]` | Other possible matches, faintly tinted by machining direction. |
409
+ | `pickedRegions` | `number[]` | Faces to mark as just clicked. |
410
+ | `hoveredFeatureIds` | `string[]` | Features to show as hovered, e.g. when hovering a list row. |
411
+ | `showEdges` | `boolean` (`true`) | Draw outlines between faces. |
412
+ | `display` | `'solid' \| 'wireframe'` (`'solid'`) | Wireframe draws face boundaries without triangle diagonals. Painted and hovered faces remain visible. |
413
+ | `theme` | `Partial<ViewerTheme>` | Override the part's colours. |
189
414
 
190
415
  **Interaction**
191
416
 
192
- | Prop | Type | What it does |
193
- | ----------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------- |
194
- | `onPick` | `(pick: PartPick) => void` | Left- or right-click on the part. |
195
- | `onHover` | `(pick: PartPick \| null) => void` | The pointer moved onto a different face, or off the part (`null`). |
196
- | `activeDirection` | `number \| null` | Only match features machined from this direction (an index into `candidateDirections`). |
197
- | `focusFeature` | `string \| null` | Zoom to this feature. The camera moves each time the value changes. |
198
- | `section` | `SectionOptions` | Cut the part open. Omit it to follow the viewer's own cut. See [Section view](#section-view). |
199
- | `onSectionChange` | `(state: SectionState) => void` | Called when the cut moves or goes away. With `section`, passing it also shows the drag handle. |
200
- | `onAdjacency` | `(map) => void` | Called once per mesh with which faces touch which. |
417
+ | Prop | Type | What it does |
418
+ | ----------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------- |
419
+ | `onPick` | `(pick: PartPick) => void` | Left- or right-click on the part. |
420
+ | `onHover` | `(pick: PartPick \| null) => void` | The pointer moved onto a different face, or off the part (`null`). |
421
+ | `hover` | `boolean` (`true`) | Paint and report faces under the pointer. `false` leaves click picking on and clears any hover. |
422
+ | `activeDirection` | `number \| null` | Only match features machined from this direction (an index into `candidateDirections`). |
423
+ | `focusFeature` | `string \| null` | Zoom to this feature. The camera moves each time the value changes. |
424
+ | `section` | `SectionOptions` | Cut the part open. Omit it to follow the viewer's own cut. See [Section view](#section-view). |
425
+ | `onSectionChange` | `(state: SectionState) => void` | Called when the cut moves or goes away. With `section`, passing it also shows the drag handle. |
426
+ | `onAdjacency` | `(map) => void` | Called once per mesh with which faces touch which. |
201
427
 
202
428
  Hovering over the part is handled for you. You only need `onHover` if you want to show the hovered
203
429
  feature elsewhere in your UI.
@@ -478,7 +704,10 @@ no `section` prop:
478
704
 
479
705
  Hovering the part previews a cut through the face under the pointer; clicking places it. Three
480
706
  coloured planes stand behind the part — click one to cut along that axis, in from your side. Once
481
- there is a cut, an arrow drags it, an outlined sheet shows the cutting plane, and Escape clears it.
707
+ there is a cut, a framed plane and two-way normal-axis handle show exactly what will move; drag the
708
+ handle to move it. Keep the canvas clear by presenting `sectionMeasurement(state)` beside the host
709
+ application's section slider; it reports the depth from a picked surface, or the distance swept
710
+ through the part bounds. Escape clears it.
482
711
  The cut belongs to the viewer: `viewer.current.setSection(null)` clears it from a button outside
483
712
  the canvas, `setSection({ enabled: true, normal, offset })` sets one, and `onSectionChange` on
484
713
  `PartMesh` still reports every move.
@@ -505,7 +734,7 @@ const [offset, setOffset] = useState(0.5)
505
734
  ```
506
735
 
507
736
  The cut surface is filled in with a hatch and outlined, so the part doesn't look hollow and the cut
508
- reads as a cut. Passing `onSectionChange` also shows an arrow handle users can drag. The handler is
737
+ reads as a cut. Passing `onSectionChange` also shows the draggable plane-frame gizmo. The handler is
509
738
  called on every drag and only when the cut actually changes, so it's safe to store the value in
510
739
  state; a cut going away is reported once, with `enabled: false`.
511
740
 
@@ -625,10 +854,11 @@ go on the cube. See `DEFAULT_THEME` for every key. The default part colours are
625
854
  default lighting, so if you change one, check the other.
626
855
 
627
856
  The section cut is themed on the part too: `sectionCap` and `sectionHatch` are the cap's fill and
628
- hatch lines, `sectionHandle` the drag arrow, and `sectionOutline` the cutting-plane sheet and
629
- preview that `<SectionTool>` draws. `measure` and `measureSnap` are `<MeasureTool>`'s lines and its
630
- snap indicator; a distance's X, Y, Z legs take `AXIS_COLORS`, the same hues as the section tool's
631
- global planes.
857
+ hatch lines, `sectionHandle` overrides the drag arrow's colour, and `sectionOutline` the
858
+ cutting-plane sheet and preview that `<SectionTool>` draws. Without a `sectionHandle` override, the
859
+ drag arrow uses the cutting plane's direction colour. `measure` and `measureSnap` are
860
+ `<MeasureTool>`'s lines and its snap indicator; a distance's X, Y, Z legs take `AXIS_COLORS`, the
861
+ same hues as the section tool's global planes.
632
862
 
633
863
  `HIGHLIGHT_COLORS` has the standard selection colours (`default`, `toolIssue`, `geometryIssue`).
634
864
  `DIRECTION_COLORS` has the 9 direction colours, which repeat for parts with more than 9 directions.
@@ -872,12 +1102,13 @@ is a complete app built this way, with no API key needed.
872
1102
 
873
1103
  ### Hooks
874
1104
 
875
- | Hook | Use inside `<Viewer>` to… |
876
- | --------------------- | -------------------------------------------------------------------------------- |
877
- | `useViewerControls()` | Get `fit`, `reset`, `setView`, `setViewDirection`, `frameBox`, and `setSection`. |
878
- | `useSectionStore()` | Read, set, or subscribe to the viewer's own cut. |
879
- | `useContentBox()` | Get the part's bounding box (a `THREE.Box3`, empty until loaded). |
880
- | `useTapGuard()` | Check whether a pointer event was a click and not a drag. |
1105
+ | Hook | Where | What it does |
1106
+ | --------------------- | -------------------------------- | -------------------------------------------------------------------------------- |
1107
+ | `useViewerControls()` | Inside `<Viewer>` | Get `fit`, `reset`, `setView`, `setViewDirection`, `frameBox`, and `setSection`. |
1108
+ | `useViewerToolbar()` | Inside `<ViewerToolbarProvider>` | Get the actions and state supplied to the provider. |
1109
+ | `useSectionStore()` | Inside `<Viewer>` | Read, set, or subscribe to the viewer's own cut. |
1110
+ | `useContentBox()` | Inside `<Viewer>` | Get the part's bounding box (a `THREE.Box3`, empty until loaded). |
1111
+ | `useTapGuard()` | Inside `<Viewer>` | Check whether a pointer event was a click and not a drag. |
881
1112
 
882
1113
  ### Helpers
883
1114
 
@@ -903,11 +1134,12 @@ use. They're listed in `dist/index.d.ts`.
903
1134
 
904
1135
  `ViewerProps`, `ViewerHandle`, `ViewerView`, `Projection`, `ControlScheme`, `EnginePartProps`,
905
1136
  `PartMeshProps`, `PartPick`, `PickModifiers`, `PartModel`, `PartModelFeature`, `PartModelRegion`,
906
- `FeatureTag`, `FeatureType`, `Vec3`, `FeatureHighlight`, `RegionHighlight`, `SectionOptions`,
1137
+ `FeatureTag`, `FeatureType`, `Vec3`, `FeatureHighlight`, `RegionHighlight`, `FocusOptions`, `SectionOptions`,
907
1138
  `SectionState`, `SectionPlacement`, `SectionToolProps`, `SectionStore`, `MeasureToolProps`,
908
1139
  `MeasureMode`, `Measurement`, `DistanceMeasurement`, `AngleMeasurement`, `Snap`, `SnapKind`,
909
1140
  `ViewerTheme`, `ViewName`, `DirectionArrowsProps`, `NamedDirection`, `GridProps`, `AxesProps`,
910
- `ViewCubeProps`.
1141
+ `ViewCubeProps`, `ViewerToolbarProps`, `ViewerToolbarControlsProps`, `ViewerToolbarControls`,
1142
+ `ViewerToolbarControl`.
911
1143
 
912
1144
  `FeatureType` and `ShapeKind` accept any string, because newer Engine versions add new values.
913
1145
  Handle values you don't recognize.
Binary file