@toolpath/viewer 1.4.0 → 2.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/README.md +364 -55
- package/dist/assets/banana.glb +0 -0
- package/dist/{chunk-HH3W5T42.js → chunk-4TBLMR4Z.js} +727 -168
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/index.d.ts +281 -19
- package/dist/index.js +999 -415
- package/dist/{normalize-DHOSNiuO.d.ts → normalize-CvnV9Ggh.d.ts} +55 -18
- package/dist/toolbar.css +108 -0
- package/package.json +5 -4
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> translucent geometry, GLB, box or cylinder stock, included in Fit
|
|
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,298 @@ 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.
|
|
115
|
-
| `pick.
|
|
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 `<Stock>` for a box, cylinder, prepared geometry or GLB bytes. Stock and part coordinates
|
|
150
|
+
must use the same millimetre, Z-up frame. Stock is included in Fit and Reset while
|
|
151
|
+
keeping section tools, measurements, the grid, and direction arrows sized to the finished part.
|
|
152
|
+
Stock does not intercept clicks or get clipped by the part's section plane.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { useMemo, useRef, useState } from 'react'
|
|
156
|
+
import {
|
|
157
|
+
Axes,
|
|
158
|
+
Stock,
|
|
159
|
+
DirectionArrows,
|
|
160
|
+
PartMesh,
|
|
161
|
+
Viewer,
|
|
162
|
+
directionHighlights,
|
|
163
|
+
} from '@toolpath/viewer'
|
|
164
|
+
import type { PartModel, ViewerHandle } from '@toolpath/viewer'
|
|
165
|
+
import type { BufferGeometry } from 'three'
|
|
166
|
+
|
|
167
|
+
export function StockPreview({ model, geometry }: { model: PartModel; geometry: BufferGeometry }) {
|
|
168
|
+
const viewer = useRef<ViewerHandle>(null)
|
|
169
|
+
const [stock, setStock] = useState(false)
|
|
170
|
+
const [axes, setAxes] = useState(true)
|
|
171
|
+
const [directions, setDirections] = useState(false)
|
|
172
|
+
const [direction, setDirection] = useState<number | null>(null)
|
|
173
|
+
const [wireframe, setWireframe] = useState(false)
|
|
174
|
+
const colors = useMemo(
|
|
175
|
+
() => (directions ? directionHighlights(model, direction) : []),
|
|
176
|
+
[model, direction, directions],
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<>
|
|
181
|
+
<Viewer ref={viewer} style={{ height: 500 }}>
|
|
182
|
+
<PartMesh
|
|
183
|
+
model={model}
|
|
184
|
+
geometry={geometry}
|
|
185
|
+
display={wireframe ? 'wireframe' : 'solid'}
|
|
186
|
+
regionHighlights={colors}
|
|
187
|
+
activeDirection={directions ? direction : null}
|
|
188
|
+
/>
|
|
189
|
+
{stock && <Stock box={{ partGeometry: geometry, allowance: { wall: 3, floor: 3 } }} />}
|
|
190
|
+
{axes && <Axes />}
|
|
191
|
+
<DirectionArrows
|
|
192
|
+
directions={model.candidateDirections}
|
|
193
|
+
visible={directions}
|
|
194
|
+
shownDirection={direction}
|
|
195
|
+
onPickDirection={(index) => setDirection((held) => (held === index ? null : index))}
|
|
196
|
+
/>
|
|
197
|
+
</Viewer>
|
|
198
|
+
<button aria-pressed={stock} onClick={() => setStock(!stock)}>
|
|
199
|
+
Stock
|
|
200
|
+
</button>
|
|
201
|
+
<button aria-pressed={axes} onClick={() => setAxes(!axes)}>
|
|
202
|
+
Axes
|
|
203
|
+
</button>
|
|
204
|
+
<button aria-pressed={directions} onClick={() => setDirections(!directions)}>
|
|
205
|
+
By direction
|
|
206
|
+
</button>
|
|
207
|
+
<button aria-pressed={wireframe} onClick={() => setWireframe(!wireframe)}>
|
|
208
|
+
Wireframe
|
|
209
|
+
</button>
|
|
210
|
+
<button onClick={() => viewer.current?.fit()}>Fit</button>
|
|
211
|
+
</>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
For a box preview, pass `box={{ partGeometry, allowance, offset }}`. Allowance is padding
|
|
217
|
+
**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 previews, pass `box={{ partGeometry, dimensions, position, positionOffset, offset }}`
|
|
225
|
+
instead, where `dimensions` is `{ x, y, z }`. Dimensions and allowance are alternative sizing
|
|
226
|
+
modes, not simultaneous options. `position` accepts
|
|
227
|
+
`model_centered`, `offset_from_top`, or `offset_from_bottom`, and `positionOffset` is the distance
|
|
228
|
+
from the selected top or bottom bound. All fixed-box values use millimetres and the part's Z-up
|
|
229
|
+
coordinate frame.
|
|
230
|
+
Negative/non-finite allowances, non-finite coordinates, and empty or non-positive stock dimensions
|
|
231
|
+
throw `RangeError`. The 3 mm allowance above is an example, not an automatic stock recommendation.
|
|
232
|
+
|
|
233
|
+
`<Stock cylinder={{ origin, axis, diameter, length }} />` draws round stock along any axis:
|
|
234
|
+
`origin` is the centre of its base and `axis` points from the base toward its far end, in part
|
|
235
|
+
coordinates. For a preview, pass
|
|
236
|
+
`cylinder={{ partGeometry, diameter, length, position, positionOffset }}` instead. It stands along
|
|
237
|
+
the part's Z, is centred on the part's bounding box, and is placed along Z by the same `position`
|
|
238
|
+
rule as fixed-box stock. The kernel centres fixed round stock on the part's
|
|
239
|
+
smallest enclosing circle about the first setup's cutting direction, so render the resolved stock
|
|
240
|
+
once a job is planned.
|
|
241
|
+
|
|
242
|
+
`<Stock box={{ frame, lower, upper }} />` draws a block whose corners are given in a frame
|
|
243
|
+
of `location`, `axis` and `refDirection`: a point `(x, y, z)` sits at
|
|
244
|
+
`location + x·refDirection + y·(axis × refDirection) + z·axis`, with `axis` normalised and
|
|
245
|
+
`refDirection` projected square to it and normalised. That is the shape of an Engine plan's
|
|
246
|
+
`stock.resolved` box, which is squared to the setup rather than to the part. Omit `frame` when
|
|
247
|
+
`lower` and `upper` are already in part coordinates. Resolved placement and `partGeometry`
|
|
248
|
+
preview options are mutually exclusive for both shapes.
|
|
249
|
+
|
|
250
|
+
Box and cylinder sources rebuild their mesh only when their figures/options change (or the
|
|
251
|
+
preview's `partGeometry` reference changes). Inline option objects do not trigger repeated part
|
|
252
|
+
scans on ordinary renders. Figures that cannot make a
|
|
253
|
+
solid — a zero axis, a non-positive size, a frame whose axis and reference direction are parallel —
|
|
254
|
+
throw `RangeError`.
|
|
255
|
+
|
|
256
|
+
`Stock` accepts exactly one of `geometry`, `glb`, `box` or `cylinder`:
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
<Stock geometry={geometry} showEdges={false} />
|
|
260
|
+
// Or:
|
|
261
|
+
<Stock glb={glbBytes} showEdges={false} />
|
|
262
|
+
<Stock box={resolvedBox} />
|
|
263
|
+
<Stock cylinder={resolvedCylinder} />
|
|
264
|
+
<Stock box={{ partGeometry, dimensions, position, positionOffset }} />
|
|
265
|
+
<Stock cylinder={{ partGeometry, diameter, length, position, positionOffset }} />
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
All four inputs are mutually exclusive in `StockProps`. With `geometry`, the caller owns its
|
|
269
|
+
lifetime and disposal. With `box` or `cylinder`, `Stock` owns the generated geometry. With `glb`,
|
|
270
|
+
`Stock` decodes the bytes internally and disposes decoded
|
|
271
|
+
geometry when the bytes change or the component unmounts. Keep the same `ArrayBuffer` reference
|
|
272
|
+
while displaying the same artifact to avoid decoding it again on ordinary renders.
|
|
273
|
+
It renders nothing while decoding and throws decoding failures to the nearest error boundary.
|
|
274
|
+
Changing the source immediately hides the previous stock; late results from an earlier decode
|
|
275
|
+
are discarded and cleaned up.
|
|
276
|
+
|
|
277
|
+
The component does not fetch, cache or store artifacts. The caller supplies the bytes.
|
|
278
|
+
GLB node transforms are applied without centering, rescaling or changing axes:
|
|
279
|
+
Engine stock artifacts are already in part coordinates, millimetres and Z-up. Indexed geometry
|
|
280
|
+
stays indexed; missing normals are computed without requiring a part report. The parser cleans up
|
|
281
|
+
its temporary scene after preparing the returned geometry.
|
|
282
|
+
|
|
283
|
+
Engine stock files are plain GLB served with HTTP gzip, which the browser handles automatically;
|
|
284
|
+
no meshopt decoder is needed. Authentication, durable artifact copies, action selection and
|
|
285
|
+
missing-artifact fallback and artifact caching belong to the application. The example's
|
|
286
|
+
**Stock source → GLB fixture** option exercises synthetic blanks encoded by the services stock
|
|
287
|
+
writer. **Shape preview** exposes the separate box/cylinder shape controls.
|
|
288
|
+
|
|
289
|
+
`Stock` accepts `color`, `emissive` (default black), `flatShading` (default `false`),
|
|
290
|
+
`opacity` (default `0.2`), `edgeColor`, `edgeOpacity`, and `showEdges`. Emissive shading
|
|
291
|
+
and flat shading change only the material, not the source's vertices, indices or normals.
|
|
292
|
+
`renderStyle="legacy-workpiece"` reproduces legacy Workpiece rendering: white with dark emissive
|
|
293
|
+
shading, per-face shading, 20% opacity, two shared-geometry surface passes and black 50%-opacity
|
|
294
|
+
edges drawn before surfaces (rather than over them). The default `overlay` style is unchanged.
|
|
295
|
+
|
|
296
|
+
For action-to-action playback, `retainPrevious` keeps the current decoded GLB visible until
|
|
297
|
+
the next GLB is ready, then swaps meshes without a blank frame. Key the stock's parent by
|
|
298
|
+
part/calculation identity (not action) to prevent retaining geometry across unrelated runs.
|
|
299
|
+
The default still hides the previous source during decoding. Geometry is disposed after a
|
|
300
|
+
replacement commits; cancelled/late results are discarded.
|
|
301
|
+
The outline is built the first time it is shown, so a large stock mesh with `showEdges={false}`
|
|
302
|
+
costs no edge pass. All source types accept the same appearance props. Caller-provided geometry is never disposed; the
|
|
303
|
+
components dispose their own materials and outlines. Conditionally mount stock to toggle it.
|
|
304
|
+
Toggling stock preserves the camera; Fit/Reset then frames the visible stock together with the part.
|
|
305
|
+
|
|
306
|
+
**Migration from 1.x:** `BoxStock` and `BoxStockProps` have been removed. Use
|
|
307
|
+
`<Stock box={{ partGeometry, allowance, offset }} />` for allowance-based previews, or
|
|
308
|
+
`<Stock box={{ partGeometry, dimensions, position, positionOffset, offset }} />` for fixed
|
|
309
|
+
dimensions. No preview-builder helper is required. `Stock geometry={geometry}` remains supported.
|
|
310
|
+
`StockProps` is now a union type alias, so `interface X extends StockProps` becomes
|
|
311
|
+
`type X = StockProps & { … }`; `StockAppearanceProps` names the shared appearance props alone.
|
|
312
|
+
|
|
313
|
+
`directionHighlights(model, activeDirection?)` returns region colors in the same palette as
|
|
314
|
+
`DirectionArrows`. For a face with several owners, the most specific feature wins, followed by
|
|
315
|
+
candidate-direction order and feature tag. Unmatched directions are left unpainted. Hover and
|
|
316
|
+
selection still paint over this wash. Scoping by direction filters ownership; it does not hide
|
|
317
|
+
the rest of the model or claim that an unpainted face cannot be manufactured.
|
|
318
|
+
|
|
319
|
+
Wireframe keeps the existing semantic edges, including rear edges, and keeps faces available for
|
|
320
|
+
picking, measuring, and section placement. Highlighted faces and section caps remain visible.
|
|
321
|
+
`showEdges` controls solid-mode outlines; wireframe always shows them. The example's bottom toolbar
|
|
322
|
+
makes wireframe and direction coloring mutually exclusive and includes a direction legend and
|
|
323
|
+
contextual Section/Measure controls.
|
|
324
|
+
|
|
325
|
+
### `<ViewerToolbar>`
|
|
326
|
+
|
|
327
|
+
`ViewerToolbar` is a composable, viewer-scoped toolbar for camera, display, section, and measurement
|
|
328
|
+
controls. Import its stylesheet alongside your application stylesheet:
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
import '@toolpath/viewer/toolbar.css'
|
|
332
|
+
import { ViewerToolbar, ViewerToolbarProvider } from '@toolpath/viewer'
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Give `ViewerToolbarProvider` the current values and callbacks from the host app, then place the
|
|
336
|
+
toolbar and viewer inside it. The toolbar owns no application state or behavior: it only reads the
|
|
337
|
+
provider. Omit `children` for every configured control in the standard order, or compose the
|
|
338
|
+
individual buttons to use only the controls and order your application needs.
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
<ViewerToolbarProvider
|
|
342
|
+
controls={{
|
|
343
|
+
stock: { pressed: showStock, onClick: () => setShowStock((shown) => !shown) },
|
|
344
|
+
measure: { pressed: measuring, onClick: toggleMeasuring },
|
|
345
|
+
fit: { onClick: () => viewer.current?.fit() },
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
348
|
+
<ViewerToolbar>
|
|
349
|
+
<ViewerToolbar.Controls>
|
|
350
|
+
<ViewerToolbar.MeasureButton />
|
|
351
|
+
<ViewerToolbar.Divider />
|
|
352
|
+
<ViewerToolbar.StockButton />
|
|
353
|
+
<ViewerToolbar.FitButton />
|
|
354
|
+
</ViewerToolbar.Controls>
|
|
355
|
+
</ViewerToolbar>
|
|
356
|
+
<Viewer ref={viewer}>…</Viewer>
|
|
357
|
+
</ViewerToolbarProvider>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`ViewerToolbar.Controls`, `Divider`, and the thirteen `*Button` components are available on
|
|
361
|
+
`ViewerToolbar`: `StockButton`, `AxesButton`, `GridButton`, `BananaButton`, `DirectionsButton`,
|
|
362
|
+
`HoverButton`, `FocusButton`, `WireframeButton`, `SectionButton`, `MeasureButton`, `FitButton`,
|
|
363
|
+
`ResetButton`, and `TopButton`. A rendered button requires its matching provider control. For an
|
|
364
|
+
application-specific toolbar component, `useViewerToolbar()` reads the same controls from inside
|
|
365
|
+
the provider.
|
|
366
|
+
|
|
367
|
+
#### Styling hooks
|
|
368
|
+
|
|
369
|
+
Every viewer, hover-card, and toolbar DOM boundary has a stable class name. Your `className` on
|
|
370
|
+
`<Viewer>`, `<ViewerToolbar>`, or `<HoverCard>` is appended to the component's own class; it never
|
|
371
|
+
replaces the hook. The toolbar stylesheet uses the same names, so import it for the default look or
|
|
372
|
+
override any of these selectors in your application stylesheet.
|
|
373
|
+
|
|
374
|
+
| Component | Class | Element |
|
|
375
|
+
| ----------------- | ------------------------- | --------------------------------- |
|
|
376
|
+
| `<Viewer>` | `viewer-root` | Wrapper around the canvas |
|
|
377
|
+
| `<Viewer>` | `viewer-canvas-container` | R3F canvas event container |
|
|
378
|
+
| `<Viewer>` | `viewer-canvas-frame` | R3F's canvas-sizing frame |
|
|
379
|
+
| `<Viewer>` | `viewer-canvas` | The `<canvas>` itself |
|
|
380
|
+
| `<HoverCard>` | `viewer-hover-card` | Cursor-following tooltip shell |
|
|
381
|
+
| `<ViewerToolbar>` | `viewer-toolbar-stack` | Outer toolbar stack |
|
|
382
|
+
| `<ViewerToolbar>` | `viewer-toolbar` | Controls group |
|
|
383
|
+
| `<ViewerToolbar>` | `viewer-toolbar-button` | Each standard control button |
|
|
384
|
+
| `<ViewerToolbar>` | `viewer-toolbar-icon` | SVG within a standard control |
|
|
385
|
+
| `<ViewerToolbar>` | `viewer-toolbar-tooltip` | Label shown on button hover/focus |
|
|
386
|
+
| `<ViewerToolbar>` | `viewer-toolbar-divider` | Separator between control groups |
|
|
387
|
+
|
|
388
|
+
The following `data-*` attributes identify generated elements without depending on labels, which
|
|
389
|
+
can change with state or localization:
|
|
390
|
+
|
|
391
|
+
| Attribute | Element and values |
|
|
392
|
+
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
393
|
+
| `data-viewer-root="true"` | `<Viewer>` wrapper |
|
|
394
|
+
| `data-viewer-canvas-container="true"` | R3F canvas event container |
|
|
395
|
+
| `data-viewer-canvas-frame="true"` | R3F canvas-sizing frame |
|
|
396
|
+
| `data-viewer-canvas="true"` | The `<canvas>` itself |
|
|
397
|
+
| `data-viewer-hover-card="true"` | `<HoverCard>` shell |
|
|
398
|
+
| `data-viewer-toolbar="true"` | Toolbar stack |
|
|
399
|
+
| `data-viewer-toolbar-controls="true"` | Controls group |
|
|
400
|
+
| `data-viewer-toolbar-action` | Standard button: `stock`, `axes`, `grid`, `banana`, `directions`, `hover`, `focus`, `wireframe`, `section`, `measure`, `fit`, `reset`, or `top` |
|
|
401
|
+
| `data-viewer-toolbar-icon="true"` | Standard button SVG |
|
|
402
|
+
| `data-viewer-toolbar-tooltip="true"` | Standard button tooltip |
|
|
403
|
+
| `data-viewer-toolbar-divider="true"` | Toolbar separator |
|
|
404
|
+
|
|
405
|
+
Toggle buttons also carry standard `aria-pressed="true"` or `"false"`; use that to style their
|
|
406
|
+
current state. `Fit`, `Reset`, and `Top view` are actions rather than toggles, so they omit the
|
|
407
|
+
attribute.
|
|
408
|
+
|
|
123
409
|
### `<Viewer>`
|
|
124
410
|
|
|
125
411
|
The canvas that holds everything. Import it from `@toolpath/viewer`.
|
|
@@ -127,7 +413,7 @@ The canvas that holds everything. Import it from `@toolpath/viewer`.
|
|
|
127
413
|
| Prop | Default | What it does |
|
|
128
414
|
| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
129
415
|
| `projection` | `'orthographic'` | `'orthographic'` keeps parallel lines parallel. `'perspective'` gives depth, which helps with deep pockets. |
|
|
130
|
-
| `controls` | `'toolpath'` |
|
|
416
|
+
| `controls` | `'toolpath'` | CAD navigation preset. See [CAD controls](#cad-controls). |
|
|
131
417
|
| `zoomTo` | `'cursor'` | Zoom toward the pointer, or toward `'centre'` (often easier on a trackpad). |
|
|
132
418
|
| `freeOrbit` | `true` | Let the view keep rotating past straight-up and straight-down. |
|
|
133
419
|
| `retargetOnDoubleClick` | `true` | Double-click the part to rotate around that point. |
|
|
@@ -142,20 +428,35 @@ Changing `projection` rebuilds the canvas and returns the camera to its starting
|
|
|
142
428
|
|
|
143
429
|
The canvas is transparent. To set a background colour, style the wrapper or its parent.
|
|
144
430
|
|
|
145
|
-
####
|
|
431
|
+
#### CAD controls
|
|
432
|
+
|
|
433
|
+
`controls` changes only the navigation gestures. It does not choose a projection: the viewer stays
|
|
434
|
+
orthographic by default, and `projection="perspective"` remains an independent opt-in.
|
|
435
|
+
|
|
436
|
+
| Value | Label | Rotate | Pan | Zoom |
|
|
437
|
+
| -------------- | ---------- | ------------------------------------- | ----------------------------------------- | ----------------------------------- |
|
|
438
|
+
| `'toolpath'` | Toolpath | Left-drag | Right-drag | Scroll wheel |
|
|
439
|
+
| `'fusion'` | Fusion | Shift + middle-drag or Shift + scroll | Middle-drag, scroll, or two-finger scroll | Trackpad pinch |
|
|
440
|
+
| `'alias'` | Alias | Left-drag | Middle-drag | Scroll wheel |
|
|
441
|
+
| `'inventor'` | Inventor | Shift + middle-drag or Shift + scroll | Middle-drag, scroll, or two-finger scroll | Trackpad pinch |
|
|
442
|
+
| `'solidworks'` | SolidWorks | Middle-drag | Ctrl + middle-drag | Shift + middle-drag or scroll wheel |
|
|
443
|
+
| `'tinkercad'` | Tinkercad | Right-drag | Shift + right-drag | Scroll wheel |
|
|
444
|
+
| `'powermill'` | PowerMill | Middle-drag | Shift + middle-drag | Scroll wheel |
|
|
445
|
+
| `'onshape'` | Onshape | Right-drag | Middle-drag | Scroll wheel |
|
|
446
|
+
|
|
447
|
+
Fusion and Inventor treat a two-finger scroll as pan, Shift + two-finger scroll as orbit, and a
|
|
448
|
+
pinch (a wheel event with Ctrl set by the browser) as zoom. Other schemes use one-finger rotate,
|
|
449
|
+
two-finger pinch-and-pan, and three-finger pan on touch screens.
|
|
146
450
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 |
|
|
451
|
+
All schemes support double-clicking the part to rotate around that point and double-clicking the
|
|
452
|
+
middle mouse button to fit the whole part. Dragging never selects anything; a click only counts if
|
|
453
|
+
the pointer barely moved.
|
|
154
454
|
|
|
155
|
-
|
|
156
|
-
|
|
455
|
+
The package exports `CONTROL_SCHEME_OPTIONS`, `ControlScheme`, and `ControlSchemeOption` for a host
|
|
456
|
+
application's settings UI. It deliberately stores no preferences: pass the selected `controls`,
|
|
457
|
+
`freeOrbit`, and `zoomTo` values to `<Viewer>` from your own state or persistence layer.
|
|
157
458
|
|
|
158
|
-
|
|
459
|
+
`'toolpath'`, `freeOrbit={true}`, and `zoomTo="cursor"` are the defaults.
|
|
159
460
|
|
|
160
461
|
### `<EnginePart>`
|
|
161
462
|
|
|
@@ -176,28 +477,30 @@ Draws the part and handles clicks. You only use it directly when you
|
|
|
176
477
|
|
|
177
478
|
**Colours**
|
|
178
479
|
|
|
179
|
-
| Prop | Type
|
|
180
|
-
| ------------------- |
|
|
181
|
-
| `selection` | `string[]`
|
|
182
|
-
| `highlights` | `FeatureHighlight[]`
|
|
183
|
-
| `regionHighlights` | `RegionHighlight[]`
|
|
184
|
-
| `candidates` | `string[]`
|
|
185
|
-
| `pickedRegions` | `number[]`
|
|
186
|
-
| `hoveredFeatureIds` | `string[]`
|
|
187
|
-
| `showEdges` | `boolean` (`true`)
|
|
188
|
-
| `
|
|
480
|
+
| Prop | Type | What it does |
|
|
481
|
+
| ------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
482
|
+
| `selection` | `string[]` | Feature tags to highlight as selected (orange). |
|
|
483
|
+
| `highlights` | `FeatureHighlight[]` | Your own colour per feature, e.g. difficulty or setup. |
|
|
484
|
+
| `regionHighlights` | `RegionHighlight[]` | Your own colour per face. |
|
|
485
|
+
| `candidates` | `string[]` | Other possible matches, faintly tinted by machining direction. |
|
|
486
|
+
| `pickedRegions` | `number[]` | Faces to mark as just clicked. |
|
|
487
|
+
| `hoveredFeatureIds` | `string[]` | Features to show as hovered, e.g. when hovering a list row. |
|
|
488
|
+
| `showEdges` | `boolean` (`true`) | Draw outlines between faces. |
|
|
489
|
+
| `display` | `'solid' \| 'wireframe'` (`'solid'`) | Wireframe draws face boundaries without triangle diagonals. Painted and hovered faces remain visible. |
|
|
490
|
+
| `theme` | `Partial<ViewerTheme>` | Override the part's colours. |
|
|
189
491
|
|
|
190
492
|
**Interaction**
|
|
191
493
|
|
|
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
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
494
|
+
| Prop | Type | What it does |
|
|
495
|
+
| ----------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
496
|
+
| `onPick` | `(pick: PartPick) => void` | Left- or right-click on the part. |
|
|
497
|
+
| `onHover` | `(pick: PartPick \| null) => void` | The pointer moved onto a different face, or off the part (`null`). |
|
|
498
|
+
| `hover` | `boolean` (`true`) | Paint and report faces under the pointer. `false` leaves click picking on and clears any hover. |
|
|
499
|
+
| `activeDirection` | `number \| null` | Only match features machined from this direction (an index into `candidateDirections`). |
|
|
500
|
+
| `focusFeature` | `string \| null` | Zoom to this feature. The camera moves each time the value changes. |
|
|
501
|
+
| `section` | `SectionOptions` | Cut the part open. Omit it to follow the viewer's own cut. See [Section view](#section-view). |
|
|
502
|
+
| `onSectionChange` | `(state: SectionState) => void` | Called when the cut moves or goes away. With `section`, passing it also shows the drag handle. |
|
|
503
|
+
| `onAdjacency` | `(map) => void` | Called once per mesh with which faces touch which. |
|
|
201
504
|
|
|
202
505
|
Hovering over the part is handled for you. You only need `onHover` if you want to show the hovered
|
|
203
506
|
feature elsewhere in your UI.
|
|
@@ -478,7 +781,10 @@ no `section` prop:
|
|
|
478
781
|
|
|
479
782
|
Hovering the part previews a cut through the face under the pointer; clicking places it. Three
|
|
480
783
|
coloured planes stand behind the part — click one to cut along that axis, in from your side. Once
|
|
481
|
-
there is a cut,
|
|
784
|
+
there is a cut, a framed plane and two-way normal-axis handle show exactly what will move; drag the
|
|
785
|
+
handle to move it. Keep the canvas clear by presenting `sectionMeasurement(state)` beside the host
|
|
786
|
+
application's section slider; it reports the depth from a picked surface, or the distance swept
|
|
787
|
+
through the part bounds. Escape clears it.
|
|
482
788
|
The cut belongs to the viewer: `viewer.current.setSection(null)` clears it from a button outside
|
|
483
789
|
the canvas, `setSection({ enabled: true, normal, offset })` sets one, and `onSectionChange` on
|
|
484
790
|
`PartMesh` still reports every move.
|
|
@@ -505,7 +811,7 @@ const [offset, setOffset] = useState(0.5)
|
|
|
505
811
|
```
|
|
506
812
|
|
|
507
813
|
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
|
|
814
|
+
reads as a cut. Passing `onSectionChange` also shows the draggable plane-frame gizmo. The handler is
|
|
509
815
|
called on every drag and only when the cut actually changes, so it's safe to store the value in
|
|
510
816
|
state; a cut going away is reported once, with `enabled: false`.
|
|
511
817
|
|
|
@@ -625,10 +931,11 @@ go on the cube. See `DEFAULT_THEME` for every key. The default part colours are
|
|
|
625
931
|
default lighting, so if you change one, check the other.
|
|
626
932
|
|
|
627
933
|
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
|
|
629
|
-
preview that `<SectionTool>` draws.
|
|
630
|
-
|
|
631
|
-
|
|
934
|
+
hatch lines, `sectionHandle` overrides the drag arrow's colour, and `sectionOutline` the
|
|
935
|
+
cutting-plane sheet and preview that `<SectionTool>` draws. Without a `sectionHandle` override, the
|
|
936
|
+
drag arrow uses the cutting plane's direction colour. `measure` and `measureSnap` are
|
|
937
|
+
`<MeasureTool>`'s lines and its snap indicator; a distance's X, Y, Z legs take `AXIS_COLORS`, the
|
|
938
|
+
same hues as the section tool's global planes.
|
|
632
939
|
|
|
633
940
|
`HIGHLIGHT_COLORS` has the standard selection colours (`default`, `toolIssue`, `geometryIssue`).
|
|
634
941
|
`DIRECTION_COLORS` has the 9 direction colours, which repeat for parts with more than 9 directions.
|
|
@@ -872,12 +1179,13 @@ is a complete app built this way, with no API key needed.
|
|
|
872
1179
|
|
|
873
1180
|
### Hooks
|
|
874
1181
|
|
|
875
|
-
| Hook |
|
|
876
|
-
| --------------------- | -------------------------------------------------------------------------------- |
|
|
877
|
-
| `useViewerControls()` | Get `fit`, `reset`, `setView`, `setViewDirection`, `frameBox`, and `setSection`. |
|
|
878
|
-
| `
|
|
879
|
-
| `
|
|
880
|
-
| `
|
|
1182
|
+
| Hook | Where | What it does |
|
|
1183
|
+
| --------------------- | -------------------------------- | -------------------------------------------------------------------------------- |
|
|
1184
|
+
| `useViewerControls()` | Inside `<Viewer>` | Get `fit`, `reset`, `setView`, `setViewDirection`, `frameBox`, and `setSection`. |
|
|
1185
|
+
| `useViewerToolbar()` | Inside `<ViewerToolbarProvider>` | Get the actions and state supplied to the provider. |
|
|
1186
|
+
| `useSectionStore()` | Inside `<Viewer>` | Read, set, or subscribe to the viewer's own cut. |
|
|
1187
|
+
| `useContentBox()` | Inside `<Viewer>` | Get the part's bounding box (a `THREE.Box3`, empty until loaded). |
|
|
1188
|
+
| `useTapGuard()` | Inside `<Viewer>` | Check whether a pointer event was a click and not a drag. |
|
|
881
1189
|
|
|
882
1190
|
### Helpers
|
|
883
1191
|
|
|
@@ -903,11 +1211,12 @@ use. They're listed in `dist/index.d.ts`.
|
|
|
903
1211
|
|
|
904
1212
|
`ViewerProps`, `ViewerHandle`, `ViewerView`, `Projection`, `ControlScheme`, `EnginePartProps`,
|
|
905
1213
|
`PartMeshProps`, `PartPick`, `PickModifiers`, `PartModel`, `PartModelFeature`, `PartModelRegion`,
|
|
906
|
-
`FeatureTag`, `FeatureType`, `Vec3`, `FeatureHighlight`, `RegionHighlight`, `SectionOptions`,
|
|
1214
|
+
`FeatureTag`, `FeatureType`, `Vec3`, `FeatureHighlight`, `RegionHighlight`, `FocusOptions`, `SectionOptions`,
|
|
907
1215
|
`SectionState`, `SectionPlacement`, `SectionToolProps`, `SectionStore`, `MeasureToolProps`,
|
|
908
1216
|
`MeasureMode`, `Measurement`, `DistanceMeasurement`, `AngleMeasurement`, `Snap`, `SnapKind`,
|
|
909
1217
|
`ViewerTheme`, `ViewName`, `DirectionArrowsProps`, `NamedDirection`, `GridProps`, `AxesProps`,
|
|
910
|
-
`ViewCubeProps
|
|
1218
|
+
`ViewCubeProps`, `ViewerToolbarProps`, `ViewerToolbarControlsProps`, `ViewerToolbarControls`,
|
|
1219
|
+
`ViewerToolbarControl`.
|
|
911
1220
|
|
|
912
1221
|
`FeatureType` and `ShapeKind` accept any string, because newer Engine versions add new values.
|
|
913
1222
|
Handle values you don't recognize.
|
|
Binary file
|