@toolpath/viewer 0.2.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/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/chunk-7NQBV7EQ.js +2537 -0
- package/dist/engine/index.d.ts +118 -0
- package/dist/engine/index.js +30 -0
- package/dist/index.d.ts +637 -0
- package/dist/index.js +676 -0
- package/dist/normalize-B0HBvzGu.d.ts +868 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Toolpath
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# `@toolpath/viewer`
|
|
2
|
+
|
|
3
|
+
React Three Fiber components for exploring Toolpath Engine part meshes. The package is client-side:
|
|
4
|
+
it can be imported by an SSR application, but render `<Viewer>` from a client component.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install @toolpath/viewer react react-dom three @react-three/fiber @react-three/drei
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Toolpath Engine reports
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Axes, DirectionArrows, Grid, ViewCube, Viewer } from '@toolpath/viewer'
|
|
14
|
+
import { EnginePart } from '@toolpath/viewer/engine'
|
|
15
|
+
;<Suspense fallback={<p>Loading mesh…</p>}>
|
|
16
|
+
<Viewer style={{ height: 500 }}>
|
|
17
|
+
<EnginePart report={report} selection={selection} onPick={(pick) => setPick(pick)} />
|
|
18
|
+
<Grid />
|
|
19
|
+
<Axes />
|
|
20
|
+
<ViewCube />
|
|
21
|
+
</Viewer>
|
|
22
|
+
</Suspense>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`EnginePart` takes a `PartReportResponse` exactly as `@toolpath/api` returns it and validates it:
|
|
26
|
+
a malformed report throws `PartReportFormatError` carrying every problem it found, and one from a
|
|
27
|
+
kernel older than `0.3.0` — before `regions[]` and `featureTag` existed — throws
|
|
28
|
+
`UnsupportedKernelVersionError`. It fetches `meshGlbUrl`, falls back to `meshStlUrl`, and refuses a
|
|
29
|
+
mesh whose triangle count does not match the report, because region ranges index that buffer
|
|
30
|
+
directly and a mismatch would quietly highlight the wrong surface.
|
|
31
|
+
|
|
32
|
+
Wrap the tree in an error boundary: an expired mesh URL, a malformed report, and an old kernel all
|
|
33
|
+
arrive as thrown errors rather than as empty states. The viewer does not parse STEP — the Engine
|
|
34
|
+
analyzes STEP and emits tessellated GLB/STL for display.
|
|
35
|
+
|
|
36
|
+
## A click means several things at once
|
|
37
|
+
|
|
38
|
+
A region on the mesh is owned by **five to eight features at once** — measured on a cube, not
|
|
39
|
+
estimated. The same physical face is a `face` cut from one direction and a `wall` from others, and
|
|
40
|
+
every direction's `profile` overlaps the surfaces it traces. Nothing can reduce that to one answer,
|
|
41
|
+
so `onPick` hands over the whole set:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Viewer onPointerMissed={clearSelection}>
|
|
45
|
+
<EnginePart
|
|
46
|
+
report={report}
|
|
47
|
+
onPick={(pick) => {
|
|
48
|
+
setCandidates(pick.ranked) // every reading, best first
|
|
49
|
+
setFocused(focusForPick(pick, lastRegion, focused)) // clicking again walks them
|
|
50
|
+
setLastRegion(pick.region)
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
</Viewer>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Putting the selection down belongs to `<Viewer onPointerMissed>` rather than to
|
|
57
|
+
`onPick`: a mesh's own missed event fires whenever _that mesh_ was not hit,
|
|
58
|
+
including when the click landed on a direction arrow or a section handle, so
|
|
59
|
+
reporting it from the part made pressing an arrow clear the selection.
|
|
60
|
+
|
|
61
|
+
`ranked` orders the owners by type specificity — a hole beats the wall it is bored through, and a
|
|
62
|
+
`profile` never wins the surface it traces — then by which reading faces the camera. `best` is the
|
|
63
|
+
first of them. Pass `activeDirection` to scope a pick to one machining direction, which **filters**
|
|
64
|
+
rather than reorders: a face that direction cannot reach picks to nothing, which is a real answer
|
|
65
|
+
rather than a missed click.
|
|
66
|
+
|
|
67
|
+
## Colouring the part
|
|
68
|
+
|
|
69
|
+
A face can only be one colour, so the layers are painted weakest first and each overwrites what is
|
|
70
|
+
under it:
|
|
71
|
+
|
|
72
|
+
| Layer | Weight | What it says |
|
|
73
|
+
| ------------------- | ------ | --------------------------------------------- |
|
|
74
|
+
| `highlights` | 0.7 | your own meaning — a difficulty band, a setup |
|
|
75
|
+
| `regionHighlights` | 0.7 | the same, on named faces rather than features |
|
|
76
|
+
| `candidates` | 0.4 | what a click could have meant, per direction |
|
|
77
|
+
| `selection` | 1.0 | the features being read |
|
|
78
|
+
| `hoveredFeatureIds` | 0.85 | a list row under the pointer |
|
|
79
|
+
| the hovered face | 0.85 | what the pointer is on, tracked for you |
|
|
80
|
+
|
|
81
|
+
Highlighting is a texture write rather than a material change, so lighting every feature of a part
|
|
82
|
+
costs one mesh, one material, and one draw call.
|
|
83
|
+
|
|
84
|
+
## Sectioning
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<EnginePart
|
|
88
|
+
report={report}
|
|
89
|
+
section={{ enabled: true, normal: { x: 0, y: 0, z: 1 }, offset }}
|
|
90
|
+
onSectionChange={(state) => setOffset(state.offset)}
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`normal` points into the half that stays and `offset` runs 0 (whole) to 1 (gone). To cut from a
|
|
95
|
+
surface instead, turn a pick into a placement with `sectionFromPick(pick)` and pass it as `plane`,
|
|
96
|
+
moving it with `depth` in model units. The cut is capped rather than left hollow, and its arrow can
|
|
97
|
+
be dragged; `onSectionChange` reports every move, including the drag's.
|
|
98
|
+
|
|
99
|
+
## Camera and controls
|
|
100
|
+
|
|
101
|
+
Use a ref or `useViewerControls()` beneath a viewer to call `fit`, `reset`, `setView`, and
|
|
102
|
+
`setViewDirection`. The part data is Z-up millimetres and the camera says so.
|
|
103
|
+
|
|
104
|
+
- `controls="toolpath"` (default) — left-drag orbits, right-drag pans.
|
|
105
|
+
- `controls="fusion"` — middle-drag and two-finger scroll pan, shift orbits, pinch zooms.
|
|
106
|
+
- `projection="orthographic"` — parallel edges stay parallel, so two features the same size
|
|
107
|
+
measure the same size wherever they sit.
|
|
108
|
+
- `freeOrbit` (default on) — orbiting past a pole keeps going instead of sticking there.
|
|
109
|
+
|
|
110
|
+
`<ViewCube>` offers all 26 standard views: six faces, twelve edge chamfers, and eight corners, so
|
|
111
|
+
an isometric is a click rather than a drag. `<Grid>` sizes itself from the part — 5 mm cells under a
|
|
112
|
+
50 mm cube, 50 mm under a 900 mm plate — and sits on the part's underside. `<DirectionArrows>`
|
|
113
|
+
draws the directions a part can be held in, each in its own colour, aimed inward because a
|
|
114
|
+
machining direction is the direction the tool comes from.
|
|
115
|
+
|
|
116
|
+
## Driving it without the Engine
|
|
117
|
+
|
|
118
|
+
`PartMesh` takes a `PartModel` and a `BufferGeometry` directly, so the viewer can be driven from a
|
|
119
|
+
file, a fixture, or geometry you built yourself — see `examples/react-viewer`. The mesh must be
|
|
120
|
+
non-indexed: highlighting is a per-vertex region attribute, and a vertex shared between two regions
|
|
121
|
+
has no single value to carry.
|