beckhoff-xts-viewer-3d 4.9.0 → 5.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 +145 -661
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +425 -17
- package/dist/index.d.ts +425 -17
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/screenshots/10-feed-segments.png +0 -0
- package/docs/screenshots/README.md +14 -13
- package/package.json +54 -29
package/README.md
CHANGED
|
@@ -3,21 +3,45 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/beckhoff-xts-viewer-3d)
|
|
4
4
|
[](https://www.npmjs.com/package/beckhoff-xts-viewer-3d-assets)
|
|
5
5
|
[](LICENSE)
|
|
6
|
-
[](https://github.com/philippleidig/beckhoff-xts-viewer-3d/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
A
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
selection, calibration, multi-track placement, **PBR-realistic lighting**,
|
|
13
|
-
soft shadows, and a CAD-style ViewCube.
|
|
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.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
viewer
|
|
17
|
-
declarative API.
|
|
13
|
+
It covers the same ground as the official 2D `Beckhoff.TwinCAT.HMI.XTS.Controls`
|
|
14
|
+
viewer, using real CAD geometry in three dimensions.
|
|
18
15
|
|
|
19
16
|

|
|
20
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
|
+
|
|
21
45
|
```tsx
|
|
22
46
|
import { XtsViewer3D } from 'beckhoff-xts-viewer-3d';
|
|
23
47
|
|
|
@@ -43,542 +67,81 @@ import { XtsViewer3D } from 'beckhoff-xts-viewer-3d';
|
|
|
43
67
|
],
|
|
44
68
|
},
|
|
45
69
|
],
|
|
46
|
-
movers: [
|
|
47
|
-
{ index: 0, id: 0, partOid: 0, partPositionMm: 200 },
|
|
48
|
-
],
|
|
70
|
+
movers: [{ index: 0, id: 0, partOid: 0, partPositionMm: 200 }],
|
|
49
71
|
},
|
|
50
72
|
],
|
|
51
73
|
}}
|
|
52
74
|
/>;
|
|
53
75
|
```
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
`beckhoff-xts-viewer-3d-assets` release on jsDelivr
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Table of contents
|
|
62
|
-
|
|
63
|
-
- [Highlights](#highlights)
|
|
64
|
-
- [Gallery](#gallery)
|
|
65
|
-
- [Installation](#installation)
|
|
66
|
-
- [Getting started](#getting-started)
|
|
67
|
-
- [GLB assets — zero-config by default](#1-glb-assets--zero-config-by-default)
|
|
68
|
-
- [Build a config](#2-build-a-config)
|
|
69
|
-
- [Drive mover positions](#3-drive-mover-positions)
|
|
70
|
-
- [Read selection + errors](#4-read-selection--errors)
|
|
71
|
-
- [Detect mover collisions](#5-detect-mover-collisions)
|
|
72
|
-
- [Capture screenshots](#6-capture-screenshots)
|
|
73
|
-
- [Live 2D plan view (orthographic top-down)](#7-live-2d-plan-view-orthographic-top-down)
|
|
74
|
-
- [Fly the camera to an object (focusOn)](#8-fly-the-camera-to-an-object-focuson)
|
|
75
|
-
- [Mark zones with Areas](#9-mark-zones-with-areas)
|
|
76
|
-
- [Stator heatmap](#10-stator-heatmap)
|
|
77
|
-
- [Track direction + zero offset](#11-track-direction--zero-offset)
|
|
78
|
-
- [Realism + performance](#realism--performance)
|
|
79
|
-
- [Troubleshooting](#troubleshooting)
|
|
80
|
-
- [Documentation](#documentation)
|
|
81
|
-
- [Development setup](#development-setup)
|
|
82
|
-
- [Releasing](#releasing)
|
|
83
|
-
- [License](#license)
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Highlights
|
|
88
|
-
|
|
89
|
-
- **Every module + mover variant** in the Beckhoff catalogue — Standard AT,
|
|
90
|
-
Eco AT2200, NCT (AT2002 / AT2102 + AT8200 tools), Hygienic ATH, plus Hepco
|
|
91
|
-
GFX2-1TC-S25. Drop in a STP file, wire the type — done.
|
|
92
|
-
- **Path math 1:1 with the 2D reference** — straights, ±22.5° / ±45° curves,
|
|
93
|
-
AT2050 / ATH2050 180° clothoid kehres. Module-to-module C0/C1 continuity
|
|
94
|
-
guaranteed by golden fixtures.
|
|
95
|
-
- **PBR-realistic rendering** — ACES filmic tone mapping, image-based
|
|
96
|
-
lighting via a procedural indoor environment, anisotropic textures, soft
|
|
97
|
-
PCF shadows. Zero asset fetches; the environment map is built on-device
|
|
98
|
-
from three.js's `RoomEnvironment`. All tunable via `display.*` props or
|
|
99
|
-
off-by-default for direct-lighting parity.
|
|
100
|
-
- **Mover animation via imperative ref** — `viewerRef.current.setMoverPositions(...)`
|
|
101
|
-
bypasses React reconciliation entirely (useFrame + Three.js scene-graph),
|
|
102
|
-
so a 60-Hz drive loop costs zero React renders.
|
|
103
|
-
- **Selection + drive status** — click to select modules / movers; the GLB
|
|
104
|
-
itself tints / blinks (no wireframe overlays), and a small modern 3D status
|
|
105
|
-
icon (warning ▲ / error ⊙) floats above affected objects.
|
|
106
|
-
- **Multi-track** — per-XPU `trackTransform` (position + rotation +
|
|
107
|
-
uniform scale) so independent XTS lines can sit side-by-side in one scene.
|
|
108
|
-
- **Live calibration overrides** — push origin-correction edits to module /
|
|
109
|
-
mover / tool sidecars in real time without touching files.
|
|
110
|
-
- **Stations, Areas, Dimensions, InfoBars** — full 2D feature parity, plus
|
|
111
|
-
camera-facing mm-value labels, intermediate ticks, and a 7-shape stop-marker
|
|
112
|
-
palette (Diamond / Tick / Sphere / Cone / Cube / Cylinder / None) settable
|
|
113
|
-
per-station. **Areas** are stop-position-free zone overlays (cleanroom,
|
|
114
|
-
safety loop, manual access) — text + colour, multi-part.
|
|
115
|
-
Stop-position values can be track-relative (default) or station-relative.
|
|
116
|
-
- **Stop-position ghost movers** — `display.showStopPositionMovers` renders a
|
|
117
|
-
static, semi-transparent mover GLB at every active stop, tinted to the
|
|
118
|
-
station colour by default. Useful for layout reviews ("where will the
|
|
119
|
-
mover end up").
|
|
120
|
-
- **Mover collision detection** — sub-millimetre 1D arc-length test on the
|
|
121
|
-
shared chain. One-shot via `viewerRef.current.checkMoverCollisions()` or
|
|
122
|
-
continuous via `<XtsViewer3D collisionDetection={{ enabled, onCollisionsChange }} />`.
|
|
123
|
-
Closed-loop seam handled automatically.
|
|
124
|
-
- **Stator heatmap overlay** — coloured tube along each part's centerline
|
|
125
|
-
with vertex colours linearly interpolated across consumer-supplied
|
|
126
|
-
`(positionMm, value)` samples. Default green → red gradient; configurable
|
|
127
|
-
min / max colours, thickness, opacity, lateral / vertical offset.
|
|
128
|
-
- **Screenshots** — `viewerRef.current.exportScreenshot({ mode })` renders to
|
|
129
|
-
an offscreen target at any resolution. `'current'` captures the live camera,
|
|
130
|
-
`'top-down'` produces a 2D-viewer-style overhead AABB-fit, `'custom'`
|
|
131
|
-
reproduces a saved `CameraState`. Returns a Blob plus camera state +
|
|
132
|
-
bounding box for reproducible exports.
|
|
133
|
-
- **Track position frame** — per-XPU `positionFrame: { direction, originMm }`
|
|
134
|
-
remaps every `partPositionMm`-style value (movers, stations, areas, stops,
|
|
135
|
-
ghosts, world transforms) into the host's coordinate convention. Reverse
|
|
136
|
-
direction or shift zero without editing any other field.
|
|
137
|
-
- **Custom assets** — static, mover-bound, all-movers; opacity + scale
|
|
138
|
-
per-instance, never leaks back into the source GLB.
|
|
139
|
-
- **CAD ViewCube** — opt-in, snap to standard orthogonal views.
|
|
140
|
-
- **Live 2D plan view** — `projection="orthographic"` flips the live canvas to
|
|
141
|
-
a flat top-down view, pixel-consistent with the `'top-down'` screenshot, with
|
|
142
|
-
no WebGL-context remount and rotation auto-locked.
|
|
143
|
-
- **Animated focus** — `viewerRef.current.focusOn({ kind: 'station' | 'area' |
|
|
144
|
-
'mover' | 'module' | 'scene', ... })` flies the camera so the whole target
|
|
145
|
-
fits the frame, in both 3D and 2D.
|
|
146
|
-
- **Imperative ref API** — `zoomToFit` / `frameTopDown` / `focusOn` /
|
|
147
|
-
`setCamera` / `getCamera` /
|
|
148
|
-
`getMoverWorldTransform` / `getBoundingBox` / `exportModel` /
|
|
149
|
-
`exportScreenshot` / `setMoverPosition(s)` (Record or
|
|
150
|
-
`MoverPositionEntry[]` indexed by `MoverConfig.index`) / `getMoverPosition` /
|
|
151
|
-
`setModuleStatuses` / `clearModuleStatuses` / `checkMoverCollisions` /
|
|
152
|
-
`reloadAssets`.
|
|
153
|
-
- **Designed for scale** — the `⚡ Perf stress` demo runs three ovals × 250
|
|
154
|
-
movers each (= 750 simultaneously animated movers) without React commits
|
|
155
|
-
during the steady state.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Gallery
|
|
160
|
-
|
|
161
|
-
| | |
|
|
162
|
-
|---|---|
|
|
163
|
-
| <br>**Multi-track placement** — two independent XTS lines composed via `trackTransform`. | <br>**Stations + Areas** — cleanroom / safety-area zone overlays, station tubes with stop markers. |
|
|
164
|
-
| <br>**Stator heatmap** — vertex-colour gradient along the centerline, fed from your live drive currents. | <br>**Sub-mm collision detection** — continuous monitor with banner; pair-wise 1D arc-length test on the shared chain. |
|
|
165
|
-
| <br>**Drive status** — emissive blink at 1 Hz on the GLB itself + camera-facing 3D icons (▲ warning, ⊙ error). | <br>**Perf stress** — 750 movers animated at 60 Hz with zero React commits in steady state. |
|
|
166
|
-
| <br>**PCF-soft shadows + IBL** — opt-in shadows on a transparent canvas; image-based lighting on by default. | <br>**`exportScreenshot('top-down')`** — orthographic, AABB-fit, mirrors the 2D viewer convention. |
|
|
167
|
-
|
|
168
|
-
---
|
|
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.
|
|
169
81
|
|
|
170
|
-
##
|
|
82
|
+
## Features
|
|
171
83
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
84
|
+
**Geometry and layout.** The full Beckhoff module and mover catalogue —
|
|
85
|
+
Standard AT, Eco AT2200, NCT (AT2002 / AT2102 with AT8200 tools), Hygienic ATH
|
|
86
|
+
and Hepco GFX2 — with straights, ±22.5° and ±45° curves and the AT2050 / ATH2050
|
|
87
|
+
180° clothoid reversal. Module-to-module C0/C1 continuity is pinned by golden
|
|
88
|
+
fixtures. Per-XPU `trackTransform` places independent lines in one scene, and
|
|
89
|
+
`positionFrame` remaps every position value into the host's coordinate
|
|
90
|
+
convention without touching the rest of the config.
|
|
175
91
|
|
|
176
|
-
|
|
92
|
+
**Mover motion.** `setMoverPositions()` writes into a per-component store that
|
|
93
|
+
`useFrame` drains directly into the three.js scene graph, so a 60 Hz drive loop
|
|
94
|
+
produces no React renders. An instanced fast path batches mover bodies into a
|
|
95
|
+
single draw call where per-mover scene nodes are not needed.
|
|
177
96
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
97
|
+
**Rendering.** ACES filmic tone mapping, image-based lighting from a
|
|
98
|
+
procedurally built indoor environment, anisotropic filtering and optional
|
|
99
|
+
PCF-soft shadows, all switchable through `display.*`. SSAO and bloom are
|
|
100
|
+
available when the optional post-processing peers are installed.
|
|
181
101
|
|
|
182
|
-
|
|
102
|
+
**Interaction and status.** Click selection for modules and movers, tinting the
|
|
103
|
+
GLB itself rather than overlaying wireframes; drive-status blink plus
|
|
104
|
+
camera-facing warning and error icons; feed-segment (Einspeisestrang) tinting
|
|
105
|
+
that wraps the seam of a closed loop; stations, areas, dimensions and info bars;
|
|
106
|
+
stop-position ghost movers; and a stator heatmap driven by
|
|
107
|
+
`(positionMm, value)` samples.
|
|
183
108
|
|
|
184
|
-
-
|
|
185
|
-
-
|
|
186
|
-
- Modern bundler (Vite, Webpack, Next.js, Remix, Astro, plain CRA — all fine)
|
|
109
|
+
**Analysis.** Sub-millimetre mover collision detection, as a one-shot call or a
|
|
110
|
+
continuous monitor, plus module-level collision probes.
|
|
187
111
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
## Getting started
|
|
195
|
-
|
|
196
|
-
### 1. GLB assets — zero-config by default
|
|
197
|
-
|
|
198
|
-
The viewer's default `assetsBaseUrl` points at the jsDelivr CDN, version-
|
|
199
|
-
pinned to the matching [`beckhoff-xts-viewer-3d-assets`](https://www.npmjs.com/package/beckhoff-xts-viewer-3d-assets)
|
|
200
|
-
release:
|
|
201
|
-
|
|
202
|
-
```text
|
|
203
|
-
https://cdn.jsdelivr.net/npm/beckhoff-xts-viewer-3d-assets@<version>/models
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
So in the typical case there is **nothing to install or host** — drop in
|
|
207
|
-
`<XtsViewer3D config={…} />` and the GLBs stream from jsDelivr.
|
|
208
|
-
|
|
209
|
-
Calibration metadata (origin-correction, path lengths, AABBs) is compiled
|
|
210
|
-
into the JS bundle, so the viewer never makes a sidecar HTTP request for
|
|
211
|
-
known module / mover / tool types.
|
|
212
|
-
|
|
213
|
-
#### Self-hosting
|
|
214
|
-
|
|
215
|
-
If you can't reach jsDelivr (air-gapped network, corporate proxy, regulated
|
|
216
|
-
environment), install the assets package and serve the GLBs yourself:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
npm install beckhoff-xts-viewer-3d-assets
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Copy `node_modules/beckhoff-xts-viewer-3d-assets/models` into your
|
|
223
|
-
app's static folder during build, and point at it:
|
|
224
|
-
|
|
225
|
-
```tsx
|
|
226
|
-
<XtsViewer3D config={cfg} assetsBaseUrl="/models" />
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Or use any other URL prefix:
|
|
230
|
-
|
|
231
|
-
```tsx
|
|
232
|
-
<XtsViewer3D config={cfg} assetsBaseUrl="https://cdn.example.com/xts/" />
|
|
233
|
-
```
|
|
112
|
+
**Export.** `exportScreenshot()` renders offscreen at any resolution with MSAA,
|
|
113
|
+
in current-camera, top-down or saved-camera mode. `beginFrameCapture()` opens a
|
|
114
|
+
reusable session whose `grab()` returns a frame synchronously without image
|
|
115
|
+
encoding, and keeps producing frames while the window is minimised. Both bypass
|
|
116
|
+
the post-processing chain; the result reports whether that happened.
|
|
234
117
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
~28 MB. Splitting them lets `npm install beckhoff-xts-viewer-3d`
|
|
239
|
-
stay tiny, while the assets are version-pinned and fetched on demand from
|
|
240
|
-
a globally cached CDN.
|
|
241
|
-
|
|
242
|
-
### 2. Build a config
|
|
243
|
-
|
|
244
|
-
The minimum: one `ProcessingUnitConfig` with one `Part` containing your
|
|
245
|
-
module list and one or more `Mover`s. See the snippet at the top of this
|
|
246
|
-
README for a working oval loop.
|
|
247
|
-
|
|
248
|
-
The component fills its parent (`width: 100%; height: 100%`) — make sure
|
|
249
|
-
the parent has a definite height. The canvas is **transparent**: whatever
|
|
250
|
-
sits behind the host element shows through, so wrap in a styled container
|
|
251
|
-
if you want a solid backdrop.
|
|
252
|
-
|
|
253
|
-
### 3. Drive mover positions
|
|
254
|
-
|
|
255
|
-
Movers don't animate themselves — your app pushes positions through the
|
|
256
|
-
imperative ref:
|
|
257
|
-
|
|
258
|
-
```tsx
|
|
259
|
-
import { useRef, useEffect } from 'react';
|
|
260
|
-
import { XtsViewer3D, type XtsViewer3DRef } from 'beckhoff-xts-viewer-3d';
|
|
261
|
-
|
|
262
|
-
function App() {
|
|
263
|
-
const viewerRef = useRef<XtsViewer3DRef>(null);
|
|
264
|
-
|
|
265
|
-
useEffect(() => {
|
|
266
|
-
let raf = 0;
|
|
267
|
-
const tick = (t: number) => {
|
|
268
|
-
viewerRef.current?.setMoverPosition(0, (t / 5) % 3000);
|
|
269
|
-
raf = requestAnimationFrame(tick);
|
|
270
|
-
};
|
|
271
|
-
raf = requestAnimationFrame(tick);
|
|
272
|
-
return () => cancelAnimationFrame(raf);
|
|
273
|
-
}, []);
|
|
274
|
-
|
|
275
|
-
return <XtsViewer3D ref={viewerRef} config={config} />;
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
The push goes straight into a per-component store; `<XtsMover>` reads it in
|
|
280
|
-
`useFrame` and mutates its Three.js group. **Zero React renders per tick.**
|
|
281
|
-
|
|
282
|
-
For state-driven flows the legacy contract still works: just keep
|
|
283
|
-
`MoverConfig.partPositionMm` updated in the config and pass the new config
|
|
284
|
-
through. Whichever you set last wins (imperative store > config prop).
|
|
285
|
-
|
|
286
|
-
### 4. Read selection + errors
|
|
287
|
-
|
|
288
|
-
```tsx
|
|
289
|
-
<XtsViewer3D
|
|
290
|
-
config={config}
|
|
291
|
-
selectionMode="Single" // | 'Off' | 'Multi'
|
|
292
|
-
onSelectionChange={(s) => console.log('selection', s)}
|
|
293
|
-
onError={(err) => console.error(err.code, err.message)}
|
|
294
|
-
/>
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
`SelectionState` carries `{ modules: ModuleRef[], movers: MoverRef[] }`.
|
|
298
|
-
Errors flow through `onError` with typed codes:
|
|
299
|
-
`asset-load-failed | unknown-module-type | unmatched-clothoid-half | …`.
|
|
300
|
-
|
|
301
|
-
### 5. Detect mover collisions
|
|
302
|
-
|
|
303
|
-
Two flavours — pick one.
|
|
304
|
-
|
|
305
|
-
**Continuous monitoring** (callback fires when the collision set changes):
|
|
306
|
-
|
|
307
|
-
```tsx
|
|
308
|
-
import type { MoverCollision } from 'beckhoff-xts-viewer-3d';
|
|
309
|
-
|
|
310
|
-
<XtsViewer3D
|
|
311
|
-
config={config}
|
|
312
|
-
collisionDetection={{
|
|
313
|
-
enabled: true,
|
|
314
|
-
warningGapMm: 0, // 0 = real collisions; > 0 also reports near-misses
|
|
315
|
-
intervalMs: 0, // 0 = check every frame; e.g. 50 for 20 Hz
|
|
316
|
-
onCollisionsChange: (collisions: MoverCollision[]) => {
|
|
317
|
-
// Fires only when the set actually changes (pair appears / disappears
|
|
318
|
-
// / penetrationMm shifts by > 0.01 mm).
|
|
319
|
-
if (collisions.length) console.warn('crash:', collisions[0]);
|
|
320
|
-
},
|
|
321
|
-
}}
|
|
322
|
-
/>;
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**One-shot query** via the imperative ref:
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
const list = viewerRef.current?.checkMoverCollisions({ warningGapMm: 5 });
|
|
329
|
-
// → MoverCollision[] sorted deepest-penetration first
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Each `MoverCollision` carries:
|
|
333
|
-
|
|
334
|
-
```ts
|
|
335
|
-
{
|
|
336
|
-
a: MoverRef; b: MoverRef;
|
|
337
|
-
idA: string; idB: string;
|
|
338
|
-
penetrationMm: number; // > 0 = overlap, 0 = touching, < 0 = warning gap
|
|
339
|
-
positionAMm: number; positionBMm: number;
|
|
340
|
-
pathLengthAMm: number; pathLengthBMm: number;
|
|
341
|
-
viaWraparound: boolean; // true when measured across the closed-loop seam
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
Sub-millimetre accurate (pure float64 arc-length math). The check covers
|
|
346
|
-
movers travelling on the same chain; cross-track collisions in multi-XPU
|
|
347
|
-
setups are out of scope.
|
|
348
|
-
|
|
349
|
-
### 6. Capture screenshots
|
|
350
|
-
|
|
351
|
-
Trigger from anywhere in your app via the imperative ref:
|
|
352
|
-
|
|
353
|
-
```tsx
|
|
354
|
-
const viewer = useRef<XtsViewer3DRef>(null);
|
|
355
|
-
|
|
356
|
-
async function saveTopDown() {
|
|
357
|
-
const result = await viewer.current!.exportScreenshot({
|
|
358
|
-
mode: 'top-down', // | 'current' | 'custom'
|
|
359
|
-
pixelRatio: 2, // 2× sharpness even on a 1× display
|
|
360
|
-
paddingFactor: 1.15,
|
|
361
|
-
format: 'png', // | 'jpeg' | 'webp'
|
|
362
|
-
backgroundColor: null, // null = transparent PNG; '#0e1116' for a solid bg
|
|
363
|
-
});
|
|
364
|
-
// result: { blob, widthPx, heightPx, camera, boundingBoxMm, mode }
|
|
365
|
-
saveAs(result.blob, `layout-${Date.now()}.png`);
|
|
366
|
-
}
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
`'top-down'` mirrors the 2D viewer: orthographic camera centred on the
|
|
370
|
-
scene's bounding box, world +X = image right, world +Y = image up.
|
|
371
|
-
`'custom'` lets you reproduce a saved framing exactly:
|
|
372
|
-
|
|
373
|
-
```tsx
|
|
374
|
-
viewer.current!.exportScreenshot({
|
|
375
|
-
mode: 'custom',
|
|
376
|
-
camera: previousResult.camera, // round-trip a saved CameraState
|
|
377
|
-
});
|
|
378
|
-
```
|
|
118
|
+
**Camera.** Orthographic top-down projection for a live 2D plan view, an
|
|
119
|
+
animated `focusOn()` for stations, areas, movers, modules or the whole scene,
|
|
120
|
+
and an opt-in CAD ViewCube.
|
|
379
121
|
|
|
380
|
-
|
|
381
|
-
running at full speed, no `preserveDrawingBuffer` perf cost.
|
|
382
|
-
|
|
383
|
-
### 7. Live 2D plan view (orthographic top-down)
|
|
384
|
-
|
|
385
|
-
Set `projection="orthographic"` to switch the **live** canvas into a flat 2D
|
|
386
|
-
plan view — straight down +Z, world +Y up — that is pixel-consistent with
|
|
387
|
-
`exportScreenshot({ mode: 'top-down' })`. Switching at runtime does **not**
|
|
388
|
-
recreate the WebGL context, so toggling between 3D and 2D is instant.
|
|
389
|
-
|
|
390
|
-
```tsx
|
|
391
|
-
const [is2D, setIs2D] = useState(false);
|
|
392
|
-
|
|
393
|
-
<XtsViewer3D
|
|
394
|
-
config={config}
|
|
395
|
-
projection={is2D ? 'orthographic' : 'perspective'}
|
|
396
|
-
// shadows add nothing to a flat plan — drop them in 2D
|
|
397
|
-
display={is2D ? { ...display, shadows: false } : display}
|
|
398
|
-
/>
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
In orthographic top-down, **rotation is auto-disabled** (a 2D plan has no
|
|
402
|
-
meaningful orbit); pan and zoom stay on. Set `lock={{ rotate: false }}` to
|
|
403
|
-
opt rotation back in. The frustum re-fits automatically on container resize
|
|
404
|
-
and whenever the scene's bounding box changes. From the ref, `frameTopDown()`
|
|
405
|
-
re-fits on demand and `zoomToFit()` adjusts the ortho frustum (instead of
|
|
406
|
-
dollying) when the live camera is orthographic.
|
|
407
|
-
|
|
408
|
-
### 8. Fly the camera to an object (focusOn)
|
|
409
|
-
|
|
410
|
-
`viewerRef.current.focusOn(target, opts)` animates the camera so a **station,
|
|
411
|
-
area, mover, module** — or the whole `scene` — fits the frame. In perspective
|
|
412
|
-
the current view angle is preserved (the camera only dollies + re-centres); in
|
|
413
|
-
orthographic top-down the frustum re-frames and the camera pans straight over
|
|
414
|
-
the target. Works in both 2D and 3D.
|
|
415
|
-
|
|
416
|
-
```tsx
|
|
417
|
-
// Frame a station, 700 ms ease-in-out (defaults)
|
|
418
|
-
viewerRef.current?.focusOn({ kind: 'station', stationId: 3 });
|
|
419
|
-
|
|
420
|
-
// Frame a mover, faster
|
|
421
|
-
viewerRef.current?.focusOn(
|
|
422
|
-
{ kind: 'mover', ref: { processingUnitObjectId: 0, moverIndex: 2 } },
|
|
423
|
-
{ durationMs: 500 },
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
// Frame an area / a module / the whole scene
|
|
427
|
-
viewerRef.current?.focusOn({ kind: 'area', areaId: 1 });
|
|
428
|
-
viewerRef.current?.focusOn({
|
|
429
|
-
kind: 'module',
|
|
430
|
-
ref: { processingUnitObjectId: 0, partObjectId: 10, moduleIndex: 4 },
|
|
431
|
-
});
|
|
432
|
-
viewerRef.current?.focusOn({ kind: 'scene' }, { durationMs: 0 }); // 0 = jump
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
`FocusOptions`: `durationMs` (default `700`, `0` jumps), `paddingFactor`
|
|
436
|
-
(default `1.2` perspective / `1.1` ortho), `easing` (`'easeInOutCubic'` |
|
|
437
|
-
`'linear'`). A new `focusOn` call supersedes any in-flight animation.
|
|
438
|
-
|
|
439
|
-
### 9. Mark zones with Areas
|
|
440
|
-
|
|
441
|
-
Areas are stop-position-free range overlays — like Stations, but with no
|
|
442
|
-
markers. Use them for cleanroom / safety-area / manual-access
|
|
443
|
-
zones that don't drive any mover behaviour:
|
|
444
|
-
|
|
445
|
-
```tsx
|
|
446
|
-
<XtsViewer3D
|
|
447
|
-
config={{
|
|
448
|
-
processingUnits: [/* … */],
|
|
449
|
-
areas: [
|
|
450
|
-
{
|
|
451
|
-
areaId: 1,
|
|
452
|
-
description: 'Cleanroom',
|
|
453
|
-
isEnabled: true,
|
|
454
|
-
partOids: [0],
|
|
455
|
-
startPositionOnPart: 250,
|
|
456
|
-
endPositionOnPart: 1000,
|
|
457
|
-
color: 0xff_4d_9d_e0, // ARGB
|
|
458
|
-
},
|
|
459
|
-
],
|
|
460
|
-
}}
|
|
461
|
-
display={{
|
|
462
|
-
areaOptions: {
|
|
463
|
-
thicknessMm: 8,
|
|
464
|
-
displacementMm: 60,
|
|
465
|
-
opacity: 0.7,
|
|
466
|
-
showAreaDescription: true,
|
|
467
|
-
},
|
|
468
|
-
}}
|
|
469
|
-
/>;
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
### 10. Stator heatmap
|
|
473
|
-
|
|
474
|
-
Coloured tube along each part's centerline, vertex colours interpolated
|
|
475
|
-
across consumer-supplied `(positionMm, value)` samples — perfect for
|
|
476
|
-
streaming live drive currents or stator temperatures:
|
|
477
|
-
|
|
478
|
-
```tsx
|
|
479
|
-
import type { StatorHeatmap } from 'beckhoff-xts-viewer-3d';
|
|
480
|
-
|
|
481
|
-
const heatmap: StatorHeatmap = {
|
|
482
|
-
parts: [{ partOid: 0, samples: [{ positionMm: 0, value: 25 }, /* … */] }],
|
|
483
|
-
min: 0, max: 100,
|
|
484
|
-
minColor: '#22c55e', // default green
|
|
485
|
-
maxColor: '#ef4444', // default red
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
<XtsViewer3D
|
|
489
|
-
config={config}
|
|
490
|
-
statorHeatmap={heatmap}
|
|
491
|
-
display={{ showStatorHeatmap: true }}
|
|
492
|
-
/>;
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
### 11. Track direction + zero offset
|
|
496
|
-
|
|
497
|
-
When the host machine uses a different sign convention or zero point
|
|
498
|
-
than the GLB chain, set a `positionFrame` on the XPU. Movers, stations,
|
|
499
|
-
areas, stops, ghosts, and `getMoverWorldTransform` all follow:
|
|
500
|
-
|
|
501
|
-
```tsx
|
|
502
|
-
processingUnits: [
|
|
503
|
-
{
|
|
504
|
-
objectId: 0,
|
|
505
|
-
moverType: 'AT9014_0055',
|
|
506
|
-
positionFrame: { direction: 'negative', originMm: 1500 },
|
|
507
|
-
parts: [/* … */],
|
|
508
|
-
movers: [
|
|
509
|
-
{ index: 0, id: 0, partOid: 0, partPositionMm: 0 },
|
|
510
|
-
],
|
|
511
|
-
},
|
|
512
|
-
]
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
For more — every prop, every ref method, every helper, every type —
|
|
516
|
-
read **[docs/USING-THE-COMPONENT.md](docs/USING-THE-COMPONENT.md)**.
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
## Realism + performance
|
|
521
|
-
|
|
522
|
-
The viewer is tuned to look like a CAD-quality render out of the box while
|
|
523
|
-
holding 60 Hz on mid-range integrated GPUs. Every realism feature is
|
|
524
|
-
opt-out via `display.*` so consumers who liked the old direct-lighting
|
|
525
|
-
look can revert with a single prop.
|
|
526
|
-
|
|
527
|
-
| Feature | Default | Knob | Cost |
|
|
528
|
-
|---|---|---|---|
|
|
529
|
-
| Image-based lighting (PMREM-prefiltered `RoomEnvironment`) | **on** | `display.environmentLighting` | one-time PMREM build (~1.5 MB GPU); zero per-frame overhead beyond standard PBR shader |
|
|
530
|
-
| Environment intensity | `0.4` | `display.environmentIntensity` | — |
|
|
531
|
-
| ACES Filmic tone mapping | **on** | `display.toneMapping` (`'aces' \| 'linear' \| 'reinhard' \| 'cineon' \| 'agx' \| 'none'`) | shader-side, ~free |
|
|
532
|
-
| Tone-mapping exposure | `1.0` | `display.toneMappingExposure` | — |
|
|
533
|
-
| Anisotropic texture filtering (max hardware) | **on** | (always on; pure sampler state) | none |
|
|
534
|
-
| `castShadow` / `receiveShadow` on every GLB mesh | **on** | (auto) | none unless shadows are enabled |
|
|
535
|
-
| PCF-soft shadows on the directional light | **off** | `display.shadows` | one extra render pass on the shadow map (4096²) |
|
|
536
|
-
| Shadow-catcher plane (transparent ground) | tied to `shadows` | — | trivial |
|
|
537
|
-
| Hemisphere fill (when env lighting is off) | **on (when no IBL)** | (auto) | vertex-frequency |
|
|
538
|
-
| Auto-pause render loop when tab hidden | **on** | `performance.autoPauseOnHidden` | — |
|
|
539
|
-
| Mover updates via `MoverPositionStore` (no React commits) | **always** | — | — |
|
|
540
|
-
|
|
541
|
-
The PBR pipeline picks up automatically: any `MeshStandardMaterial` /
|
|
542
|
-
`MeshPhysicalMaterial` already exported in your GLBs (the standard glTF
|
|
543
|
-
metal/rough workflow) inherits `scene.environment` for reflections.
|
|
544
|
-
Custom GLBs with non-PBR materials are unaffected — they render exactly
|
|
545
|
-
the same.
|
|
546
|
-
|
|
547
|
-
If you need flat direct-lighting parity:
|
|
548
|
-
|
|
549
|
-
```tsx
|
|
550
|
-
<XtsViewer3D
|
|
551
|
-
config={config}
|
|
552
|
-
display={{
|
|
553
|
-
environmentLighting: false, // disable IBL
|
|
554
|
-
toneMapping: 'none', // no tone-mapping curve
|
|
555
|
-
}}
|
|
556
|
-
/>
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
Stress baseline: `⚡ Perf stress` (3 ovals × 250 movers = 750 mover groups
|
|
560
|
-
animated at 60 Hz) runs steady at ~16 ms/frame in Chrome on a mid-range
|
|
561
|
-
laptop with 0 React commits in steady state, IBL + ACES + anisotropy on.
|
|
122
|
+
## Gallery
|
|
562
123
|
|
|
563
|
-
|
|
124
|
+
| | |
|
|
125
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
126
|
+
| <br>Two independent lines composed with `trackTransform`. | <br>Station tubes with stop markers, and zone overlays for cleanroom or safety areas. |
|
|
127
|
+
| <br>Vertex-colour gradient along the centerline, fed from live drive currents. | <br>Continuous pair-wise arc-length collision monitoring. |
|
|
128
|
+
| <br>Emissive blink on the GLB plus camera-facing warning and error icons. | <br>750 movers animated at 60 Hz with no React commits in steady state. |
|
|
129
|
+
| <br>Opt-in PCF-soft shadows on a transparent canvas, image-based lighting on by default. | <br>`exportScreenshot({ mode: 'top-down' })` — orthographic and AABB-fit. |
|
|
130
|
+
| <br>`feedSegmentHighlights` tints whole electrical strands; the pink one wraps the loop seam. | |
|
|
564
131
|
|
|
565
132
|
## Troubleshooting
|
|
566
133
|
|
|
567
|
-
### Modules render as yellow boxes
|
|
134
|
+
### Modules render as yellow boxes and movers as blue boxes
|
|
568
135
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
No `models/*.glb` requests show up in the Network panel.
|
|
136
|
+
The viewer is showing wireframe placeholders because no GLB was accepted. The
|
|
137
|
+
console also reports `THREE.WARNING: Multiple instances of Three.js being
|
|
138
|
+
imported`, and no `models/*.glb` requests appear in the network panel.
|
|
573
139
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
`
|
|
577
|
-
copies produce two `THREE.*` namespaces; `useGLTF`'s `instanceof`
|
|
578
|
-
checks fail across the boundary and silently reject every parsed scene.
|
|
140
|
+
This happens when a transitive dependency pins its own copy of `three`. Two
|
|
141
|
+
copies mean two `THREE.*` namespaces, and the `instanceof` checks inside
|
|
142
|
+
`useGLTF` reject every parsed scene across that boundary.
|
|
579
143
|
|
|
580
|
-
|
|
581
|
-
`resolve.dedupe`:
|
|
144
|
+
Deduplicate `three` in your bundler:
|
|
582
145
|
|
|
583
146
|
```ts
|
|
584
147
|
// vite.config.ts
|
|
@@ -588,167 +151,88 @@ export default defineConfig({
|
|
|
588
151
|
});
|
|
589
152
|
```
|
|
590
153
|
|
|
591
|
-
Webpack
|
|
592
|
-
[
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
---
|
|
154
|
+
For Webpack and Next.js, alias `three` to your root `node_modules/three`.
|
|
155
|
+
[Bundler configuration](docs/USING-THE-COMPONENT.md#bundler-configuration--deduplicate-three)
|
|
156
|
+
has the full snippets and explains how to clear Vite's pre-bundle cache
|
|
157
|
+
afterwards.
|
|
597
158
|
|
|
598
159
|
## Documentation
|
|
599
160
|
|
|
600
|
-
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
-
|
|
607
|
-
one-click release flow: how to publish a new version, npm Trusted
|
|
608
|
-
Publishers setup, calibration safety, emergency manual release.
|
|
609
|
-
- **[docs/screenshots/README.md](docs/screenshots/README.md)** —
|
|
610
|
-
how to refresh the README + docs gallery from the playground.
|
|
161
|
+
- [Using the component](docs/USING-THE-COMPONENT.md) — every prop, every ref
|
|
162
|
+
method, asset hosting, recipes and performance tuning.
|
|
163
|
+
- [Adding a module](docs/ADDING-A-MODULE.md) — taking a new module, mover or
|
|
164
|
+
tool type from STP file to calibrated GLB.
|
|
165
|
+
- [Performance](docs/PERFORMANCE.md) — the asset compression pipeline and the
|
|
166
|
+
runtime budget.
|
|
167
|
+
- [Releasing](docs/RELEASING.md) — the automated publish flow.
|
|
611
168
|
|
|
612
|
-
|
|
169
|
+
## Development
|
|
613
170
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
This repo is a pnpm workspace — `pnpm-lock.yaml` is the source of
|
|
617
|
-
truth and `npm ci` / `npm install` will not work. Get pnpm via
|
|
618
|
-
`npm install -g pnpm@10` (or any other installer), then:
|
|
171
|
+
The repo is a pnpm workspace; `pnpm-lock.yaml` is the source of truth, so
|
|
172
|
+
`npm install` will not work. Install pnpm 10, then:
|
|
619
173
|
|
|
620
174
|
```bash
|
|
621
175
|
pnpm install
|
|
622
|
-
pnpm
|
|
176
|
+
pnpm lint
|
|
623
177
|
pnpm typecheck
|
|
178
|
+
pnpm test
|
|
179
|
+
pnpm run test:coverage
|
|
180
|
+
pnpm build
|
|
624
181
|
```
|
|
625
182
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
feature — selection, calibration, composer, multi-track, shadows, ViewCube,
|
|
630
|
-
drive-status icons, the perf stress test, IBL toggle, intensity slider.
|
|
631
|
-
|
|
632
|
-
```bash
|
|
633
|
-
pnpm dev # http://127.0.0.1:5173
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
Sidebar controls let you switch demos, animate movers, toggle shadows /
|
|
637
|
-
IBL / ViewCube / theme, drag-and-drop tracks together in the composer,
|
|
638
|
-
and live-edit calibration overrides.
|
|
639
|
-
|
|
640
|
-
### Build the library
|
|
641
|
-
|
|
642
|
-
```bash
|
|
643
|
-
pnpm build # → dist/
|
|
644
|
-
pnpm playground:build
|
|
645
|
-
```
|
|
183
|
+
`pnpm dev` starts the playground at `http://127.0.0.1:5173`. It exercises every
|
|
184
|
+
feature and lets you switch demos, drive movers, toggle lighting and shadows,
|
|
185
|
+
compose multi-track layouts and live-edit calibration overrides.
|
|
646
186
|
|
|
647
187
|
### Asset pipeline
|
|
648
188
|
|
|
649
|
-
CAD
|
|
650
|
-
|
|
189
|
+
CAD sources in `stepfiles/*.stp` are converted to runtime GLBs plus per-asset
|
|
190
|
+
JSON sidecars:
|
|
651
191
|
|
|
652
192
|
```bash
|
|
653
|
-
pnpm assets:convert # STP → GLB
|
|
193
|
+
pnpm assets:convert # STP → GLB
|
|
654
194
|
pnpm assets:inspect # refresh docs/data/glb-inspection.json
|
|
655
|
-
pnpm assets:generate-sidecars # module .meta.json
|
|
195
|
+
pnpm assets:generate-sidecars # module .meta.json origin corrections
|
|
656
196
|
pnpm assets:generate-mover-sidecars
|
|
657
197
|
```
|
|
658
198
|
|
|
659
|
-
The
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
The release pipeline **never** invokes the generators — `bundle-sidecars`
|
|
663
|
-
reads existing JSONs read-only into the JS bundle. See
|
|
664
|
-
[`docs/RELEASING.md`](docs/RELEASING.md#calibration-safety-the-metajson-story).
|
|
665
|
-
|
|
666
|
-
To add a new module / mover / tool type from scratch — naming convention,
|
|
667
|
-
type registration, sidecar generation, calibration workflow — follow
|
|
668
|
-
**[docs/ADDING-A-MODULE.md](docs/ADDING-A-MODULE.md)**.
|
|
669
|
-
|
|
670
|
-
### Project layout
|
|
671
|
-
|
|
672
|
-
```
|
|
673
|
-
.
|
|
674
|
-
├── src/ Library source — published as the npm package
|
|
675
|
-
│ ├── components/ <XtsViewer3D> + internal scene tree
|
|
676
|
-
│ ├── geometry/ Path math, ChainBuilder, normalizeXtsConfig, …
|
|
677
|
-
│ ├── assets/ AssetManifest, SidecarLoader, AssetLoader
|
|
678
|
-
│ └── interaction/ SelectionManager
|
|
679
|
-
├── packages/
|
|
680
|
-
│ └── assets/ Sibling npm package (GLB-only mirror)
|
|
681
|
-
├── playground/ Vite app exercising every feature
|
|
682
|
-
├── public/models/ GLBs + .meta.json sidecars (sources)
|
|
683
|
-
├── stepfiles/ Source CAD STP files (NOT shipped)
|
|
684
|
-
├── scripts/ Asset pipeline + version-sync utilities
|
|
685
|
-
├── docs/
|
|
686
|
-
│ ├── USING-THE-COMPONENT.md Consumer guide (props, ref API, recipes)
|
|
687
|
-
│ ├── ADDING-A-MODULE.md How to register a new module / mover / tool
|
|
688
|
-
│ ├── RELEASING.md How to publish a new release
|
|
689
|
-
│ ├── screenshots/ README + docs gallery
|
|
690
|
-
│ └── data/ GLB AABB inspection JSON
|
|
691
|
-
├── .github/workflows/ release.yml + release-assets.yml + deploy-docs.yml
|
|
692
|
-
└── README.md
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
### Tests
|
|
696
|
-
|
|
697
|
-
`vitest` covers path math, normalize-config, chain-building, sample helpers,
|
|
698
|
-
selection logic, asset URL composition, the composer reducer, dimension
|
|
699
|
-
ticks, and the multi-track transform composition. Run focused:
|
|
700
|
-
|
|
701
|
-
```bash
|
|
702
|
-
pnpm vitest run src/geometry/__tests__/ChainBuilder.test.ts
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
---
|
|
706
|
-
|
|
707
|
-
## Releasing
|
|
199
|
+
The generators skip existing files so hand-tuned calibration is never
|
|
200
|
+
overwritten; pass `--force` to regenerate. The release pipeline never runs them
|
|
201
|
+
and reads the sidecars read-only.
|
|
708
202
|
|
|
709
|
-
|
|
710
|
-
`main`**. [semantic-release](https://semantic-release.gitbook.io/) reads
|
|
711
|
-
your Conventional Commit messages, decides the next version, writes
|
|
712
|
-
`CHANGELOG.md`, publishes both packages to npm, and opens a GitHub
|
|
713
|
-
Release.
|
|
203
|
+
### Layout
|
|
714
204
|
|
|
715
205
|
```text
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
---
|
|
206
|
+
src/ Library source — this is what ships
|
|
207
|
+
components/ <XtsViewer3D> and the internal scene tree
|
|
208
|
+
geometry/ Path math, ChainBuilder, normalizeXtsConfig
|
|
209
|
+
assets/ AssetManifest, AssetLoader, SidecarLoader
|
|
210
|
+
interaction/ SelectionManager
|
|
211
|
+
packages/assets/ Sibling npm package: GLB-only mirror
|
|
212
|
+
playground/ Vite app exercising every feature
|
|
213
|
+
public/models/ GLBs and .meta.json calibration sidecars
|
|
214
|
+
stepfiles/ Source CAD files (not published)
|
|
215
|
+
scripts/ Asset pipeline and version-sync utilities
|
|
216
|
+
docs/ VitePress site and guides
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Releasing
|
|
220
|
+
|
|
221
|
+
Every push to `main` runs [semantic-release](https://semantic-release.gitbook.io/),
|
|
222
|
+
which derives the version from Conventional Commit messages, writes
|
|
223
|
+
`CHANGELOG.md` and publishes both packages. Never run `npm version`, edit the
|
|
224
|
+
changelog or publish by hand.
|
|
225
|
+
|
|
226
|
+
| Prefix | Bump |
|
|
227
|
+
| -------------------------------------------------- | ----- |
|
|
228
|
+
| `fix:` / `perf:` | patch |
|
|
229
|
+
| `feat:` | minor |
|
|
230
|
+
| `feat!:` / `fix!:` / `BREAKING CHANGE:` footer | major |
|
|
231
|
+
| `chore:` / `docs:` / `ci:` / `refactor:` / `test:` | none |
|
|
232
|
+
|
|
233
|
+
Preview with `pnpm release:dry-run`. [Releasing](docs/RELEASING.md) covers the
|
|
234
|
+
plugin chain, the calibration-safety guard and the manual fallback.
|
|
747
235
|
|
|
748
236
|
## License
|
|
749
237
|
|
|
750
238
|
MIT — see [LICENSE](LICENSE).
|
|
751
|
-
|
|
752
|
-
The Beckhoff CAD source files under `stepfiles/` are included for
|
|
753
|
-
verification only and remain subject to their respective Beckhoff
|
|
754
|
-
licensing terms — they are not shipped with the published npm package.
|