beckhoff-xts-viewer-3d 5.2.2 → 5.3.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 -21
- package/README.md +315 -315
- package/dist/index.cjs +7 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +742 -376
- package/dist/index.d.ts +742 -376
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/docs/screenshots/README.md +123 -123
- package/package.json +198 -194
package/README.md
CHANGED
|
@@ -1,315 +1,315 @@
|
|
|
1
|
-
# beckhoff-xts-viewer-3d
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/beckhoff-xts-viewer-3d)
|
|
4
|
-
[](https://www.npmjs.com/package/beckhoff-xts-viewer-3d-assets)
|
|
5
|
-
[](LICENSE)
|
|
6
|
-
[](https://github.com/philippleidig/beckhoff-xts-viewer-3d/actions/workflows/ci.yml)
|
|
7
|
-
|
|
8
|
-
A React component that renders Beckhoff XTS linear-motor systems — and Hepco
|
|
9
|
-
GFX rail variants — in 3D. It takes a declarative description of modules,
|
|
10
|
-
movers and tools and handles the path math, GLB loading, mover animation,
|
|
11
|
-
selection, lighting and camera work.
|
|
12
|
-
|
|
13
|
-
It covers the same ground as the official 2D `Beckhoff.TwinCAT.HMI.XTS.Controls`
|
|
14
|
-
viewer, using real CAD geometry in three dimensions.
|
|
15
|
-
|
|
16
|
-

|
|
17
|
-
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install beckhoff-xts-viewer-3d
|
|
22
|
-
npm install react react-dom three
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
| Requirement | Version | Why |
|
|
26
|
-
| ------------------------------------------------ | ------------------------ | -------------------------------------------------- |
|
|
27
|
-
| `react` / `react-dom` | `^19.0.0` | required by `@react-three/fiber` 9 |
|
|
28
|
-
| `three` | `>= 0.181.0` | the viewer uses the console-hook API added in r181 |
|
|
29
|
-
| `@react-three/fiber` | `>= 9.0.0` | |
|
|
30
|
-
| `@react-three/drei` | `>= 10.0.0` | |
|
|
31
|
-
| `three-stdlib` | `>= 2.36.0` | GLTF, KTX2 and Meshopt loaders |
|
|
32
|
-
| `postprocessing` + `@react-three/postprocessing` | `>= 6.39.2` / `>= 3.0.0` | optional — only needed for SSAO and bloom |
|
|
33
|
-
| Node | `>= 20` | build tooling only; the runtime is browser ESM |
|
|
34
|
-
|
|
35
|
-
The package is `"type": "module"` and ships ESM and CJS from
|
|
36
|
-
`./dist/index.{js,cjs,d.ts}`. `sideEffects: false`, so unused exports
|
|
37
|
-
tree-shake out.
|
|
38
|
-
|
|
39
|
-
Any modern bundler works (Vite, Webpack, Next.js, Remix, Astro). If your
|
|
40
|
-
bundler installs more than one copy of `three`, deduplicate it — see
|
|
41
|
-
[Troubleshooting](#troubleshooting).
|
|
42
|
-
|
|
43
|
-
## Minimal example
|
|
44
|
-
|
|
45
|
-
```tsx
|
|
46
|
-
import { XtsViewer3D } from 'beckhoff-xts-viewer-3d';
|
|
47
|
-
|
|
48
|
-
<XtsViewer3D
|
|
49
|
-
config={{
|
|
50
|
-
processingUnits: [
|
|
51
|
-
{
|
|
52
|
-
objectId: 0,
|
|
53
|
-
moverType: 'AT9014_0055',
|
|
54
|
-
parts: [
|
|
55
|
-
{
|
|
56
|
-
objectId: 0,
|
|
57
|
-
globalNumber: 0,
|
|
58
|
-
modules: [
|
|
59
|
-
{ moduleType: 'AT2001_0250', globalNumber: 1 },
|
|
60
|
-
{ moduleType: 'AT2000_0250', globalNumber: 2 },
|
|
61
|
-
{ moduleType: 'AT2050_0500', globalNumber: 3 },
|
|
62
|
-
{ moduleType: 'AT2050_0501', globalNumber: 4 },
|
|
63
|
-
{ moduleType: 'AT2001_0250', globalNumber: 5 },
|
|
64
|
-
{ moduleType: 'AT2000_0250', globalNumber: 6 },
|
|
65
|
-
{ moduleType: 'AT2050_0500', globalNumber: 7 },
|
|
66
|
-
{ moduleType: 'AT2050_0501', globalNumber: 8 },
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
movers: [{ index: 0, id: 0, partOid: 0, partPositionMm: 200 }],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
}}
|
|
74
|
-
/>;
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
No asset hosting is required. GLBs stream from the matching
|
|
78
|
-
`beckhoff-xts-viewer-3d-assets` release on jsDelivr; see
|
|
79
|
-
[asset hosting](docs/USING-THE-COMPONENT.md#1-glb-assets--zero-config-by-default)
|
|
80
|
-
to self-host them instead.
|
|
81
|
-
|
|
82
|
-
## Module catalogue
|
|
83
|
-
|
|
84
|
-
Every image on this page comes out of the component itself, through the same
|
|
85
|
-
`exportScreenshot()` a consumer calls, and shows a layout a line would
|
|
86
|
-
actually be built as — a 5 m racetrack with four stations, a buffer loop
|
|
87
|
-
beside it. See [docs/screenshots](docs/screenshots/README.md) for the capture
|
|
88
|
-
script and [`playground/src/configs.ts`](playground/src/configs.ts) for the
|
|
89
|
-
layouts themselves.
|
|
90
|
-
|
|
91
|
-
| | | |
|
|
92
|
-
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
-
| <br>**Standard AT** — AT2001 infeed, AT2000 straights and the AT2050 180° clothoid reversal. | <br>**Eco AT2200 / AT2202** — 500 mm straights, which pair naturally with the 500 mm reversal. | <br>**NCT AT2002 / AT2102** — high modules carrying AT8200 tool carriers on the movers. |
|
|
94
|
-
| <br>**Hygienic ATH** — stainless housings, sealed joints and the ATH9011 mover. | <br>**Hepco GFX2** — the procedural GFX rail profile with a GFX2 1TC carriage instead of the Beckhoff guiding rail. | <br>**Real materials, not CAD colours** — anodised housing, stator packs, printed type plate with its LED row. |
|
|
95
|
-
|
|
96
|
-
The GLBs ship with a named PBR material library rather than the CAD sources'
|
|
97
|
-
placeholder colours — see [Asset pipeline](#asset-pipeline).
|
|
98
|
-
|
|
99
|
-
## Features
|
|
100
|
-
|
|
101
|
-
**Geometry and layout.** The full Beckhoff module and mover catalogue —
|
|
102
|
-
Standard AT, Eco AT2200, NCT (AT2002 / AT2102 with AT8200 tools), Hygienic ATH
|
|
103
|
-
and Hepco GFX2 — with straights, ±22.5° and ±45° curves and the AT2050 / ATH2050
|
|
104
|
-
180° clothoid reversal. Module-to-module C0/C1 continuity is pinned by golden
|
|
105
|
-
fixtures. Per-XPU `trackTransform` places independent lines in one scene, and
|
|
106
|
-
`positionFrame` remaps every position value into the host's coordinate
|
|
107
|
-
convention without touching the rest of the config.
|
|
108
|
-
|
|
109
|
-
**Mover motion.** `setMoverPositions()` writes into a per-component store that
|
|
110
|
-
`useFrame` drains directly into the three.js scene graph, so a 60 Hz drive loop
|
|
111
|
-
produces no React renders. An instanced fast path batches mover bodies into a
|
|
112
|
-
single draw call where per-mover scene nodes are not needed.
|
|
113
|
-
|
|
114
|
-
**Rendering.** ACES filmic tone mapping, image-based lighting from a
|
|
115
|
-
procedurally built indoor environment, anisotropic filtering and optional
|
|
116
|
-
PCF-soft shadows, all switchable through `display.*`. SSAO and bloom are
|
|
117
|
-
available when the optional post-processing peers are installed.
|
|
118
|
-
|
|
119
|
-
**Interaction and status.** Click selection for modules and movers, tinting the
|
|
120
|
-
GLB itself rather than overlaying wireframes; drive-status blink plus
|
|
121
|
-
camera-facing warning and error icons; feed-segment (Einspeisestrang) tinting
|
|
122
|
-
that wraps the seam of a closed loop; stations, areas, dimensions and info bars;
|
|
123
|
-
stop-position ghost movers; and a stator heatmap driven by
|
|
124
|
-
`(positionMm, value)` samples.
|
|
125
|
-
|
|
126
|
-
**Measuring and annotating.** CAD-style tools — point-to-point distance with
|
|
127
|
-
optional ΔX/ΔY/ΔZ guides, angle, polyline, and distance _along_ the XTS path —
|
|
128
|
-
picked in the scene with snapping to geometry corners and edges as well as to
|
|
129
|
-
mover centres, module boundaries, station stops and the track path itself.
|
|
130
|
-
Annotations pin a label anywhere, including to a mover, where they follow it
|
|
131
|
-
while it drives. Everything works the same in the 2D plan view, renders into
|
|
132
|
-
screenshots and video frames, and is fully drivable from
|
|
133
|
-
`viewerRef.current.measurements`.
|
|
134
|
-
|
|
135
|
-
**Analysis.** Sub-millimetre mover collision detection, as a one-shot call or a
|
|
136
|
-
continuous monitor, plus module-level collision probes.
|
|
137
|
-
|
|
138
|
-
**Export.** `exportScreenshot()` renders offscreen at any resolution with MSAA,
|
|
139
|
-
in current-camera, top-down or saved-camera mode. `beginFrameCapture()` opens a
|
|
140
|
-
reusable session whose `grab()` returns a frame synchronously without image
|
|
141
|
-
encoding, and keeps producing frames while the window is minimised. Both bypass
|
|
142
|
-
the post-processing chain; the result reports whether that happened.
|
|
143
|
-
|
|
144
|
-
**Camera.** Orthographic top-down projection for a live 2D plan view, an
|
|
145
|
-
animated `focusOn()` for stations, areas, movers, modules or the whole scene,
|
|
146
|
-
and an opt-in
|
|
147
|
-
|
|
148
|
-
### Measurements and annotations
|
|
149
|
-
|
|
150
|
-
```tsx
|
|
151
|
-
const viewer = useRef<XtsViewer3DRef>(null);
|
|
152
|
-
const [tool, setTool] = useState<MeasurementTool>('none');
|
|
153
|
-
|
|
154
|
-
<XtsViewer3D
|
|
155
|
-
ref={viewer}
|
|
156
|
-
config={config}
|
|
157
|
-
// The same tools work in the 3D view and the 2D plan view.
|
|
158
|
-
projection={plan2D ? 'orthographic' : 'perspective'}
|
|
159
|
-
measurement={{
|
|
160
|
-
tool, // 'distance' | 'angle' | 'path' | 'trackDistance' | 'annotation'
|
|
161
|
-
snap: { enabled: true, radiusPx: 14 },
|
|
162
|
-
onMeasurementCreate: (m, result) => console.log(m.kind, result.text),
|
|
163
|
-
}}
|
|
164
|
-
/>;
|
|
165
|
-
|
|
166
|
-
// …or place and read measurements without any user interaction:
|
|
167
|
-
viewer.current.measurements.add({
|
|
168
|
-
kind: 'distance',
|
|
169
|
-
points: [{ positionMm: [0, 0, 200] }, { positionMm: [1000, 0, 200] }],
|
|
170
|
-
});
|
|
171
|
-
viewer.current.measurements.results(); // [{ value: 1000, text: '1 000.0 mm', … }]
|
|
172
|
-
const saved = viewer.current.measurements.toJSON(); // persist, restore with fromJSON()
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
See [Measurements + annotations](docs/USING-THE-COMPONENT.md#17-measurements--annotations)
|
|
176
|
-
for the tools, the snap kinds and the full API.
|
|
177
|
-
|
|
178
|
-
## Gallery
|
|
179
|
-
|
|
180
|
-
| | |
|
|
181
|
-
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
182
|
-
| <br>A packaging line and a buffer loop side by side, placed with `trackTransform`. | <br>Station tubes with their stop markers, ghost movers parked on the stops, and cleanroom / manual-access zones. |
|
|
183
|
-
| <br>Dimensions along the track, stepped out from the modules they measure. | <br>Vertex-colour gradient along the centerline, fed from `(positionMm, value)` samples. |
|
|
184
|
-
| <br>Two movers of the line parked at a 1 mm overlap — `checkMoverCollisions()` reports the penetration. | <br>Emissive tint on the GLB plus camera-facing warning and error icons, on modules and movers alike. |
|
|
185
|
-
| <br>`feedSegmentHighlights` tints whole electrical strands; the pink one wraps the loop seam. | <br>A distance across the loop with ΔX/ΔY guides and two annotations, placed through `viewerRef.current.measurements`. |
|
|
186
|
-
| <br>Selection tints the GLB itself — two modules green, one mover orange — rather than overlaying a wireframe. | <br>Opt-in PCF-soft shadows over the procedurally built indoor environment. |
|
|
187
|
-
| <br>750 movers on three ovals, animated at 60 Hz with no React commits in steady state. | |
|
|
188
|
-
|
|
189
|
-

|
|
190
|
-
|
|
191
|
-
`exportScreenshot({ mode: 'top-down' })` — orthographic, fitted to the scene
|
|
192
|
-
AABB. The same view is available live through `projection="orthographic"`.
|
|
193
|
-
|
|
194
|
-
## Troubleshooting
|
|
195
|
-
|
|
196
|
-
### Modules render as yellow boxes and movers as blue boxes
|
|
197
|
-
|
|
198
|
-
The viewer is showing wireframe placeholders because no GLB was accepted. The
|
|
199
|
-
console also reports `THREE.WARNING: Multiple instances of Three.js being
|
|
200
|
-
imported`, and no `models/*.glb` requests appear in the network panel.
|
|
201
|
-
|
|
202
|
-
This happens when a transitive dependency pins its own copy of `three`. Two
|
|
203
|
-
copies mean two `THREE.*` namespaces, and the `instanceof` checks inside
|
|
204
|
-
`useGLTF` reject every parsed scene across that boundary.
|
|
205
|
-
|
|
206
|
-
Deduplicate `three` in your bundler:
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
// vite.config.ts
|
|
210
|
-
export default defineConfig({
|
|
211
|
-
plugins: [react()],
|
|
212
|
-
resolve: { dedupe: ['three'] },
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
For Webpack and Next.js, alias `three` to your root `node_modules/three`.
|
|
217
|
-
[Bundler configuration](docs/USING-THE-COMPONENT.md#bundler-configuration--deduplicate-three)
|
|
218
|
-
has the full snippets and explains how to clear Vite's pre-bundle cache
|
|
219
|
-
afterwards.
|
|
220
|
-
|
|
221
|
-
## Documentation
|
|
222
|
-
|
|
223
|
-
- [Using the component](docs/USING-THE-COMPONENT.md) — every prop, every ref
|
|
224
|
-
method, asset hosting, recipes and performance tuning.
|
|
225
|
-
- [Adding a module](docs/ADDING-A-MODULE.md) — taking a new module, mover or
|
|
226
|
-
tool type from STP file to calibrated GLB.
|
|
227
|
-
- [Performance](docs/PERFORMANCE.md) — the asset compression pipeline and the
|
|
228
|
-
runtime budget.
|
|
229
|
-
- [Releasing](docs/RELEASING.md) — the automated publish flow.
|
|
230
|
-
|
|
231
|
-
## Development
|
|
232
|
-
|
|
233
|
-
The repo is a pnpm workspace; `pnpm-lock.yaml` is the source of truth, so
|
|
234
|
-
`npm install` will not work. Install pnpm 10, then:
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
pnpm install
|
|
238
|
-
pnpm lint
|
|
239
|
-
pnpm typecheck
|
|
240
|
-
pnpm test
|
|
241
|
-
pnpm run test:coverage
|
|
242
|
-
pnpm build
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
`pnpm dev` starts the playground at `http://127.0.0.1:5173`. It exercises every
|
|
246
|
-
feature and lets you switch demos, drive movers, toggle lighting and shadows,
|
|
247
|
-
compose multi-track layouts and live-edit calibration overrides.
|
|
248
|
-
|
|
249
|
-
`pnpm docs:capture-screenshots` re-shoots the whole image strip on this page
|
|
250
|
-
from that playground, unattended — see
|
|
251
|
-
[docs/screenshots](docs/screenshots/README.md).
|
|
252
|
-
|
|
253
|
-
### Asset pipeline
|
|
254
|
-
|
|
255
|
-
CAD sources in `stepfiles/*.stp` are converted to runtime GLBs plus per-asset
|
|
256
|
-
JSON sidecars:
|
|
257
|
-
|
|
258
|
-
```bash
|
|
259
|
-
pnpm assets:convert # STP → GLB
|
|
260
|
-
pnpm assets:optimize # meshopt compression
|
|
261
|
-
pnpm assets:apply-materials # CAD placeholder colours → real PBR materials
|
|
262
|
-
pnpm assets:verify-colors # no authored STEP colour got lost
|
|
263
|
-
pnpm assets:inspect # refresh docs/data/glb-inspection.json
|
|
264
|
-
pnpm assets:generate-sidecars # module .meta.json origin corrections
|
|
265
|
-
pnpm assets:generate-mover-sidecars
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
The CAD sources carry Autodesk Inventor placeholder colours, not product
|
|
269
|
-
colours — an AT2 motor module's whole solid is styled #696969 — so
|
|
270
|
-
`assets:apply-materials` maps them onto the named PBR library in
|
|
271
|
-
`scripts/materialLibrary.mjs` (anodised aluminium, stainless steel, hardened
|
|
272
|
-
rail steel, plastics, printed labels, LEDs). It rewrites only the GLB's JSON
|
|
273
|
-
chunk, leaving geometry and the compressed binary chunk byte-identical, and is
|
|
274
|
-
idempotent. See [docs/ADDING-A-MODULE.md](docs/ADDING-A-MODULE.md).
|
|
275
|
-
|
|
276
|
-
The generators skip existing files so hand-tuned calibration is never
|
|
277
|
-
overwritten; pass `--force` to regenerate. The release pipeline never runs them
|
|
278
|
-
and reads the sidecars read-only.
|
|
279
|
-
|
|
280
|
-
### Layout
|
|
281
|
-
|
|
282
|
-
```text
|
|
283
|
-
src/ Library source — this is what ships
|
|
284
|
-
components/ <XtsViewer3D> and the internal scene tree
|
|
285
|
-
geometry/ Path math, ChainBuilder, normalizeXtsConfig
|
|
286
|
-
assets/ AssetManifest, AssetLoader, SidecarLoader
|
|
287
|
-
interaction/ SelectionManager
|
|
288
|
-
packages/assets/ Sibling npm package: GLB-only mirror
|
|
289
|
-
playground/ Vite app exercising every feature
|
|
290
|
-
public/models/ GLBs and .meta.json calibration sidecars
|
|
291
|
-
stepfiles/ Source CAD files (not published)
|
|
292
|
-
scripts/ Asset pipeline and version-sync utilities
|
|
293
|
-
docs/ VitePress site and guides
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Releasing
|
|
297
|
-
|
|
298
|
-
Every push to `main` runs [semantic-release](https://semantic-release.gitbook.io/),
|
|
299
|
-
which derives the version from Conventional Commit messages, writes
|
|
300
|
-
`CHANGELOG.md` and publishes both packages. Never run `npm version`, edit the
|
|
301
|
-
changelog or publish by hand.
|
|
302
|
-
|
|
303
|
-
| Prefix | Bump |
|
|
304
|
-
| -------------------------------------------------- | ----- |
|
|
305
|
-
| `fix:` / `perf:` | patch |
|
|
306
|
-
| `feat:` | minor |
|
|
307
|
-
| `feat!:` / `fix!:` / `BREAKING CHANGE:` footer | major |
|
|
308
|
-
| `chore:` / `docs:` / `ci:` / `refactor:` / `test:` | none |
|
|
309
|
-
|
|
310
|
-
Preview with `pnpm release:dry-run`. [Releasing](docs/RELEASING.md) covers the
|
|
311
|
-
plugin chain, the calibration-safety guard and the manual fallback.
|
|
312
|
-
|
|
313
|
-
## License
|
|
314
|
-
|
|
315
|
-
MIT — see [LICENSE](LICENSE).
|
|
1
|
+
# beckhoff-xts-viewer-3d
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/beckhoff-xts-viewer-3d)
|
|
4
|
+
[](https://www.npmjs.com/package/beckhoff-xts-viewer-3d-assets)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/philippleidig/beckhoff-xts-viewer-3d/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
A React component that renders Beckhoff XTS linear-motor systems — and Hepco
|
|
9
|
+
GFX rail variants — in 3D. It takes a declarative description of modules,
|
|
10
|
+
movers and tools and handles the path math, GLB loading, mover animation,
|
|
11
|
+
selection, lighting and camera work.
|
|
12
|
+
|
|
13
|
+
It covers the same ground as the official 2D `Beckhoff.TwinCAT.HMI.XTS.Controls`
|
|
14
|
+
viewer, using real CAD geometry in three dimensions.
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install beckhoff-xts-viewer-3d
|
|
22
|
+
npm install react react-dom three
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Requirement | Version | Why |
|
|
26
|
+
| ------------------------------------------------ | ------------------------ | -------------------------------------------------- |
|
|
27
|
+
| `react` / `react-dom` | `^19.0.0` | required by `@react-three/fiber` 9 |
|
|
28
|
+
| `three` | `>= 0.181.0` | the viewer uses the console-hook API added in r181 |
|
|
29
|
+
| `@react-three/fiber` | `>= 9.0.0` | |
|
|
30
|
+
| `@react-three/drei` | `>= 10.0.0` | |
|
|
31
|
+
| `three-stdlib` | `>= 2.36.0` | GLTF, KTX2 and Meshopt loaders |
|
|
32
|
+
| `postprocessing` + `@react-three/postprocessing` | `>= 6.39.2` / `>= 3.0.0` | optional — only needed for SSAO and bloom |
|
|
33
|
+
| Node | `>= 20` | build tooling only; the runtime is browser ESM |
|
|
34
|
+
|
|
35
|
+
The package is `"type": "module"` and ships ESM and CJS from
|
|
36
|
+
`./dist/index.{js,cjs,d.ts}`. `sideEffects: false`, so unused exports
|
|
37
|
+
tree-shake out.
|
|
38
|
+
|
|
39
|
+
Any modern bundler works (Vite, Webpack, Next.js, Remix, Astro). If your
|
|
40
|
+
bundler installs more than one copy of `three`, deduplicate it — see
|
|
41
|
+
[Troubleshooting](#troubleshooting).
|
|
42
|
+
|
|
43
|
+
## Minimal example
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { XtsViewer3D } from 'beckhoff-xts-viewer-3d';
|
|
47
|
+
|
|
48
|
+
<XtsViewer3D
|
|
49
|
+
config={{
|
|
50
|
+
processingUnits: [
|
|
51
|
+
{
|
|
52
|
+
objectId: 0,
|
|
53
|
+
moverType: 'AT9014_0055',
|
|
54
|
+
parts: [
|
|
55
|
+
{
|
|
56
|
+
objectId: 0,
|
|
57
|
+
globalNumber: 0,
|
|
58
|
+
modules: [
|
|
59
|
+
{ moduleType: 'AT2001_0250', globalNumber: 1 },
|
|
60
|
+
{ moduleType: 'AT2000_0250', globalNumber: 2 },
|
|
61
|
+
{ moduleType: 'AT2050_0500', globalNumber: 3 },
|
|
62
|
+
{ moduleType: 'AT2050_0501', globalNumber: 4 },
|
|
63
|
+
{ moduleType: 'AT2001_0250', globalNumber: 5 },
|
|
64
|
+
{ moduleType: 'AT2000_0250', globalNumber: 6 },
|
|
65
|
+
{ moduleType: 'AT2050_0500', globalNumber: 7 },
|
|
66
|
+
{ moduleType: 'AT2050_0501', globalNumber: 8 },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
movers: [{ index: 0, id: 0, partOid: 0, partPositionMm: 200 }],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}}
|
|
74
|
+
/>;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
No asset hosting is required. GLBs stream from the matching
|
|
78
|
+
`beckhoff-xts-viewer-3d-assets` release on jsDelivr; see
|
|
79
|
+
[asset hosting](docs/USING-THE-COMPONENT.md#1-glb-assets--zero-config-by-default)
|
|
80
|
+
to self-host them instead.
|
|
81
|
+
|
|
82
|
+
## Module catalogue
|
|
83
|
+
|
|
84
|
+
Every image on this page comes out of the component itself, through the same
|
|
85
|
+
`exportScreenshot()` a consumer calls, and shows a layout a line would
|
|
86
|
+
actually be built as — a 5 m racetrack with four stations, a buffer loop
|
|
87
|
+
beside it. See [docs/screenshots](docs/screenshots/README.md) for the capture
|
|
88
|
+
script and [`playground/src/configs.ts`](playground/src/configs.ts) for the
|
|
89
|
+
layouts themselves.
|
|
90
|
+
|
|
91
|
+
| | | |
|
|
92
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| <br>**Standard AT** — AT2001 infeed, AT2000 straights and the AT2050 180° clothoid reversal. | <br>**Eco AT2200 / AT2202** — 500 mm straights, which pair naturally with the 500 mm reversal. | <br>**NCT AT2002 / AT2102** — high modules carrying AT8200 tool carriers on the movers. |
|
|
94
|
+
| <br>**Hygienic ATH** — stainless housings, sealed joints and the ATH9011 mover. | <br>**Hepco GFX2** — the procedural GFX rail profile with a GFX2 1TC carriage instead of the Beckhoff guiding rail. | <br>**Real materials, not CAD colours** — anodised housing, stator packs, printed type plate with its LED row. |
|
|
95
|
+
|
|
96
|
+
The GLBs ship with a named PBR material library rather than the CAD sources'
|
|
97
|
+
placeholder colours — see [Asset pipeline](#asset-pipeline).
|
|
98
|
+
|
|
99
|
+
## Features
|
|
100
|
+
|
|
101
|
+
**Geometry and layout.** The full Beckhoff module and mover catalogue —
|
|
102
|
+
Standard AT, Eco AT2200, NCT (AT2002 / AT2102 with AT8200 tools), Hygienic ATH
|
|
103
|
+
and Hepco GFX2 — with straights, ±22.5° and ±45° curves and the AT2050 / ATH2050
|
|
104
|
+
180° clothoid reversal. Module-to-module C0/C1 continuity is pinned by golden
|
|
105
|
+
fixtures. Per-XPU `trackTransform` places independent lines in one scene, and
|
|
106
|
+
`positionFrame` remaps every position value into the host's coordinate
|
|
107
|
+
convention without touching the rest of the config.
|
|
108
|
+
|
|
109
|
+
**Mover motion.** `setMoverPositions()` writes into a per-component store that
|
|
110
|
+
`useFrame` drains directly into the three.js scene graph, so a 60 Hz drive loop
|
|
111
|
+
produces no React renders. An instanced fast path batches mover bodies into a
|
|
112
|
+
single draw call where per-mover scene nodes are not needed.
|
|
113
|
+
|
|
114
|
+
**Rendering.** ACES filmic tone mapping, image-based lighting from a
|
|
115
|
+
procedurally built indoor environment, anisotropic filtering and optional
|
|
116
|
+
PCF-soft shadows, all switchable through `display.*`. SSAO and bloom are
|
|
117
|
+
available when the optional post-processing peers are installed.
|
|
118
|
+
|
|
119
|
+
**Interaction and status.** Click selection for modules and movers, tinting the
|
|
120
|
+
GLB itself rather than overlaying wireframes; drive-status blink plus
|
|
121
|
+
camera-facing warning and error icons; feed-segment (Einspeisestrang) tinting
|
|
122
|
+
that wraps the seam of a closed loop; stations, areas, dimensions and info bars;
|
|
123
|
+
stop-position ghost movers; and a stator heatmap driven by
|
|
124
|
+
`(positionMm, value)` samples.
|
|
125
|
+
|
|
126
|
+
**Measuring and annotating.** CAD-style tools — point-to-point distance with
|
|
127
|
+
optional ΔX/ΔY/ΔZ guides, angle, polyline, and distance _along_ the XTS path —
|
|
128
|
+
picked in the scene with snapping to geometry corners and edges as well as to
|
|
129
|
+
mover centres, module boundaries, station stops and the track path itself.
|
|
130
|
+
Annotations pin a label anywhere, including to a mover, where they follow it
|
|
131
|
+
while it drives. Everything works the same in the 2D plan view, renders into
|
|
132
|
+
screenshots and video frames, and is fully drivable from
|
|
133
|
+
`viewerRef.current.measurements`.
|
|
134
|
+
|
|
135
|
+
**Analysis.** Sub-millimetre mover collision detection, as a one-shot call or a
|
|
136
|
+
continuous monitor, plus module-level collision probes.
|
|
137
|
+
|
|
138
|
+
**Export.** `exportScreenshot()` renders offscreen at any resolution with MSAA,
|
|
139
|
+
in current-camera, top-down or saved-camera mode. `beginFrameCapture()` opens a
|
|
140
|
+
reusable session whose `grab()` returns a frame synchronously without image
|
|
141
|
+
encoding, and keeps producing frames while the window is minimised. Both bypass
|
|
142
|
+
the post-processing chain; the result reports whether that happened.
|
|
143
|
+
|
|
144
|
+
**Camera.** Orthographic top-down projection for a live 2D plan view, an
|
|
145
|
+
animated `focusOn()` for stations, areas, movers, modules or the whole scene,
|
|
146
|
+
and an opt-in X / Y / Z orientation gizmo.
|
|
147
|
+
|
|
148
|
+
### Measurements and annotations
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
const viewer = useRef<XtsViewer3DRef>(null);
|
|
152
|
+
const [tool, setTool] = useState<MeasurementTool>('none');
|
|
153
|
+
|
|
154
|
+
<XtsViewer3D
|
|
155
|
+
ref={viewer}
|
|
156
|
+
config={config}
|
|
157
|
+
// The same tools work in the 3D view and the 2D plan view.
|
|
158
|
+
projection={plan2D ? 'orthographic' : 'perspective'}
|
|
159
|
+
measurement={{
|
|
160
|
+
tool, // 'distance' | 'angle' | 'path' | 'trackDistance' | 'annotation'
|
|
161
|
+
snap: { enabled: true, radiusPx: 14 },
|
|
162
|
+
onMeasurementCreate: (m, result) => console.log(m.kind, result.text),
|
|
163
|
+
}}
|
|
164
|
+
/>;
|
|
165
|
+
|
|
166
|
+
// …or place and read measurements without any user interaction:
|
|
167
|
+
viewer.current.measurements.add({
|
|
168
|
+
kind: 'distance',
|
|
169
|
+
points: [{ positionMm: [0, 0, 200] }, { positionMm: [1000, 0, 200] }],
|
|
170
|
+
});
|
|
171
|
+
viewer.current.measurements.results(); // [{ value: 1000, text: '1 000.0 mm', … }]
|
|
172
|
+
const saved = viewer.current.measurements.toJSON(); // persist, restore with fromJSON()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
See [Measurements + annotations](docs/USING-THE-COMPONENT.md#17-measurements--annotations)
|
|
176
|
+
for the tools, the snap kinds and the full API.
|
|
177
|
+
|
|
178
|
+
## Gallery
|
|
179
|
+
|
|
180
|
+
| | |
|
|
181
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
182
|
+
| <br>A packaging line and a buffer loop side by side, placed with `trackTransform`. | <br>Station tubes with their stop markers, ghost movers parked on the stops, and cleanroom / manual-access zones. |
|
|
183
|
+
| <br>Dimensions along the track, stepped out from the modules they measure. | <br>Vertex-colour gradient along the centerline, fed from `(positionMm, value)` samples. |
|
|
184
|
+
| <br>Two movers of the line parked at a 1 mm overlap — `checkMoverCollisions()` reports the penetration. | <br>Emissive tint on the GLB plus camera-facing warning and error icons, on modules and movers alike. |
|
|
185
|
+
| <br>`feedSegmentHighlights` tints whole electrical strands; the pink one wraps the loop seam. | <br>A distance across the loop with ΔX/ΔY guides and two annotations, placed through `viewerRef.current.measurements`. |
|
|
186
|
+
| <br>Selection tints the GLB itself — two modules green, one mover orange — rather than overlaying a wireframe. | <br>Opt-in PCF-soft shadows over the procedurally built indoor environment. |
|
|
187
|
+
| <br>750 movers on three ovals, animated at 60 Hz with no React commits in steady state. | |
|
|
188
|
+
|
|
189
|
+

|
|
190
|
+
|
|
191
|
+
`exportScreenshot({ mode: 'top-down' })` — orthographic, fitted to the scene
|
|
192
|
+
AABB. The same view is available live through `projection="orthographic"`.
|
|
193
|
+
|
|
194
|
+
## Troubleshooting
|
|
195
|
+
|
|
196
|
+
### Modules render as yellow boxes and movers as blue boxes
|
|
197
|
+
|
|
198
|
+
The viewer is showing wireframe placeholders because no GLB was accepted. The
|
|
199
|
+
console also reports `THREE.WARNING: Multiple instances of Three.js being
|
|
200
|
+
imported`, and no `models/*.glb` requests appear in the network panel.
|
|
201
|
+
|
|
202
|
+
This happens when a transitive dependency pins its own copy of `three`. Two
|
|
203
|
+
copies mean two `THREE.*` namespaces, and the `instanceof` checks inside
|
|
204
|
+
`useGLTF` reject every parsed scene across that boundary.
|
|
205
|
+
|
|
206
|
+
Deduplicate `three` in your bundler:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
// vite.config.ts
|
|
210
|
+
export default defineConfig({
|
|
211
|
+
plugins: [react()],
|
|
212
|
+
resolve: { dedupe: ['three'] },
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
For Webpack and Next.js, alias `three` to your root `node_modules/three`.
|
|
217
|
+
[Bundler configuration](docs/USING-THE-COMPONENT.md#bundler-configuration--deduplicate-three)
|
|
218
|
+
has the full snippets and explains how to clear Vite's pre-bundle cache
|
|
219
|
+
afterwards.
|
|
220
|
+
|
|
221
|
+
## Documentation
|
|
222
|
+
|
|
223
|
+
- [Using the component](docs/USING-THE-COMPONENT.md) — every prop, every ref
|
|
224
|
+
method, asset hosting, recipes and performance tuning.
|
|
225
|
+
- [Adding a module](docs/ADDING-A-MODULE.md) — taking a new module, mover or
|
|
226
|
+
tool type from STP file to calibrated GLB.
|
|
227
|
+
- [Performance](docs/PERFORMANCE.md) — the asset compression pipeline and the
|
|
228
|
+
runtime budget.
|
|
229
|
+
- [Releasing](docs/RELEASING.md) — the automated publish flow.
|
|
230
|
+
|
|
231
|
+
## Development
|
|
232
|
+
|
|
233
|
+
The repo is a pnpm workspace; `pnpm-lock.yaml` is the source of truth, so
|
|
234
|
+
`npm install` will not work. Install pnpm 10, then:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
pnpm install
|
|
238
|
+
pnpm lint
|
|
239
|
+
pnpm typecheck
|
|
240
|
+
pnpm test
|
|
241
|
+
pnpm run test:coverage
|
|
242
|
+
pnpm build
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`pnpm dev` starts the playground at `http://127.0.0.1:5173`. It exercises every
|
|
246
|
+
feature and lets you switch demos, drive movers, toggle lighting and shadows,
|
|
247
|
+
compose multi-track layouts and live-edit calibration overrides.
|
|
248
|
+
|
|
249
|
+
`pnpm docs:capture-screenshots` re-shoots the whole image strip on this page
|
|
250
|
+
from that playground, unattended — see
|
|
251
|
+
[docs/screenshots](docs/screenshots/README.md).
|
|
252
|
+
|
|
253
|
+
### Asset pipeline
|
|
254
|
+
|
|
255
|
+
CAD sources in `stepfiles/*.stp` are converted to runtime GLBs plus per-asset
|
|
256
|
+
JSON sidecars:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
pnpm assets:convert # STP → GLB
|
|
260
|
+
pnpm assets:optimize # meshopt compression
|
|
261
|
+
pnpm assets:apply-materials # CAD placeholder colours → real PBR materials
|
|
262
|
+
pnpm assets:verify-colors # no authored STEP colour got lost
|
|
263
|
+
pnpm assets:inspect # refresh docs/data/glb-inspection.json
|
|
264
|
+
pnpm assets:generate-sidecars # module .meta.json origin corrections
|
|
265
|
+
pnpm assets:generate-mover-sidecars
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The CAD sources carry Autodesk Inventor placeholder colours, not product
|
|
269
|
+
colours — an AT2 motor module's whole solid is styled #696969 — so
|
|
270
|
+
`assets:apply-materials` maps them onto the named PBR library in
|
|
271
|
+
`scripts/materialLibrary.mjs` (anodised aluminium, stainless steel, hardened
|
|
272
|
+
rail steel, plastics, printed labels, LEDs). It rewrites only the GLB's JSON
|
|
273
|
+
chunk, leaving geometry and the compressed binary chunk byte-identical, and is
|
|
274
|
+
idempotent. See [docs/ADDING-A-MODULE.md](docs/ADDING-A-MODULE.md).
|
|
275
|
+
|
|
276
|
+
The generators skip existing files so hand-tuned calibration is never
|
|
277
|
+
overwritten; pass `--force` to regenerate. The release pipeline never runs them
|
|
278
|
+
and reads the sidecars read-only.
|
|
279
|
+
|
|
280
|
+
### Layout
|
|
281
|
+
|
|
282
|
+
```text
|
|
283
|
+
src/ Library source — this is what ships
|
|
284
|
+
components/ <XtsViewer3D> and the internal scene tree
|
|
285
|
+
geometry/ Path math, ChainBuilder, normalizeXtsConfig
|
|
286
|
+
assets/ AssetManifest, AssetLoader, SidecarLoader
|
|
287
|
+
interaction/ SelectionManager
|
|
288
|
+
packages/assets/ Sibling npm package: GLB-only mirror
|
|
289
|
+
playground/ Vite app exercising every feature
|
|
290
|
+
public/models/ GLBs and .meta.json calibration sidecars
|
|
291
|
+
stepfiles/ Source CAD files (not published)
|
|
292
|
+
scripts/ Asset pipeline and version-sync utilities
|
|
293
|
+
docs/ VitePress site and guides
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Releasing
|
|
297
|
+
|
|
298
|
+
Every push to `main` runs [semantic-release](https://semantic-release.gitbook.io/),
|
|
299
|
+
which derives the version from Conventional Commit messages, writes
|
|
300
|
+
`CHANGELOG.md` and publishes both packages. Never run `npm version`, edit the
|
|
301
|
+
changelog or publish by hand.
|
|
302
|
+
|
|
303
|
+
| Prefix | Bump |
|
|
304
|
+
| -------------------------------------------------- | ----- |
|
|
305
|
+
| `fix:` / `perf:` | patch |
|
|
306
|
+
| `feat:` | minor |
|
|
307
|
+
| `feat!:` / `fix!:` / `BREAKING CHANGE:` footer | major |
|
|
308
|
+
| `chore:` / `docs:` / `ci:` / `refactor:` / `test:` | none |
|
|
309
|
+
|
|
310
|
+
Preview with `pnpm release:dry-run`. [Releasing](docs/RELEASING.md) covers the
|
|
311
|
+
plugin chain, the calibration-safety guard and the manual fallback.
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT — see [LICENSE](LICENSE).
|