beckhoff-xts-viewer-3d
v6.2.0
Published
Unofficial, independent React component for rendering Beckhoff XTS linear-motor systems in 3D using Three.js. Not a Beckhoff product and not affiliated with or endorsed by Beckhoff Automation GmbH & Co. KG.
Maintainers
Readme
⚠️ Disclaimer — this is not a Beckhoff product
beckhoff-xts-viewer-3dis an independent, unofficial open-source project. It is not developed, published, reviewed, endorsed, sponsored or supported by Beckhoff Automation GmbH & Co. KG or by HepcoMotion, and it has no affiliation with either company. Please do not contact their support about it — open an issue instead."Beckhoff", "XTS" and "TwinCAT" are trademarks of Beckhoff Automation GmbH & Co. KG; "Hepco", "HepcoMotion" and "GFX" are trademarks of Hepco Slide Systems Ltd. They are used here only descriptively, to identify the hardware this viewer renders, and remain the property of their respective owners.
The 3D geometry is derived from CAD data published by those manufacturers for engineering use. Rights to that data and to the product designs it depicts stay with their owners: the MIT license covers this project's own source code only and grants no rights to third-party CAD data, trademarks or other intellectual property. Rights holders can reach the maintainer through a GitHub issue, and any material will be removed on request.
The software is provided "as is", without warranty of any kind (see LICENSE). It is a visualization aid: not a safety device, not validated for machine control or any safety-related function, and no substitute for the manufacturer's own documentation.
beckhoff-xts-viewer-3d
A React component that renders Beckhoff XTS linear-motor systems — and Hepco GFX rail variants — in 3D. It takes a declarative description of modules, movers and tools and handles the path math, GLB loading, mover animation, selection, lighting and camera work.
It covers the same ground as the official 2D Beckhoff.TwinCAT.HMI.XTS.Controls
viewer, using real CAD geometry in three dimensions.

Installation
npm install beckhoff-xts-viewer-3d
npm install react react-dom three| Requirement | Version | Why |
| ------------------------------------------------ | ------------------------ | -------------------------------------------------- |
| react / react-dom | ^19.0.0 | required by @react-three/fiber 9 |
| three | >= 0.181.0 | the viewer uses the console-hook API added in r181 |
| @react-three/fiber | >= 9.0.0 | |
| @react-three/drei | >= 10.0.0 | |
| three-stdlib | >= 2.36.0 | GLTF, KTX2 and Meshopt loaders |
| postprocessing + @react-three/postprocessing | >= 6.39.2 / >= 3.0.0 | optional — only needed for SSAO and bloom |
| Node | >= 20 | build tooling only; the runtime is browser ESM |
The package is "type": "module" and ships ESM and CJS from
./dist/index.{js,cjs,d.ts}. sideEffects: false, so unused exports
tree-shake out.
Any modern bundler works (Vite, Webpack, Next.js, Remix, Astro). If your
bundler installs more than one copy of three, deduplicate it — see
Troubleshooting.
Minimal example
import { XtsViewer3D } from 'beckhoff-xts-viewer-3d';
<XtsViewer3D
config={{
processingUnits: [
{
objectId: 0,
moverType: 'AT9014_0055',
parts: [
{
objectId: 0,
globalNumber: 0,
modules: [
{ moduleType: 'AT2001_0250', globalNumber: 1 },
{ moduleType: 'AT2000_0250', globalNumber: 2 },
{ moduleType: 'AT2050_0500', globalNumber: 3 },
{ moduleType: 'AT2050_0501', globalNumber: 4 },
{ moduleType: 'AT2001_0250', globalNumber: 5 },
{ moduleType: 'AT2000_0250', globalNumber: 6 },
{ moduleType: 'AT2050_0500', globalNumber: 7 },
{ moduleType: 'AT2050_0501', globalNumber: 8 },
],
},
],
movers: [{ index: 0, id: 0, partOid: 0, partPositionMm: 200 }],
},
],
}}
/>;No asset hosting is required. GLBs stream from the matching
beckhoff-xts-viewer-3d-assets release on jsDelivr; see
asset hosting
to self-host them instead.
Module catalogue
Every image on this page comes out of the component itself, through the same
exportScreenshot() a consumer calls, and shows a layout a line would
actually be built as — a 5 m racetrack with four stations, a buffer loop
beside it. See docs/screenshots for the capture
script and playground/src/configs.ts for the
layouts themselves.
| | | |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
Standard AT — AT2001 infeed, AT2000 straights and the AT2050 180° clothoid reversal. |
Eco AT2200 / AT2202 — 500 mm straights, which pair naturally with the 500 mm reversal. |
NCT AT2002 / AT2102 — high modules carrying AT8200 tool carriers on the movers. |
|
Hygienic ATH — stainless housings, sealed joints and the ATH9011 mover. |
Hepco GFX2 — the procedural GFX rail profile with a GFX2 1TC carriage instead of the Beckhoff guiding rail. |
Real materials, not CAD colours — anodised housing, stator packs, printed type plate with its LED row. |
The GLBs ship with a named PBR material library rather than the CAD sources' placeholder colours — see Asset pipeline.
Features
Geometry and layout. The full Beckhoff module and mover catalogue —
Standard AT, Eco AT2200, NCT (AT2002 / AT2102 with AT8200 tools), Hygienic ATH
and Hepco GFX2 — with straights, ±22.5° and ±45° curves and the AT2050 / ATH2050
180° clothoid reversal. Both Hepco guideways are modelled as procedural
sections swept along the track and picked by the carriage on it: the TL1
1-Trak spear bar under a 1TC carriage, the PRT2 size-44 track under an
FCC one. Module-to-module C0/C1 continuity is pinned by golden
fixtures. Per-XPU trackTransform places independent lines in one scene, and
positionFrame remaps every position value into the host's coordinate
convention without touching the rest of the config.
Mover motion. setMoverPositions() writes into a per-component store that
useFrame drains directly into the three.js scene graph, so a 60 Hz drive loop
produces no React renders. An instanced fast path batches mover bodies into a
draw-call count that no longer grows with the mover count, where per-mover
scene nodes are not needed.
Rendering. ACES filmic tone mapping, image-based lighting from a
procedurally built indoor environment, anisotropic filtering and optional
PCF-soft shadows, all switchable through display.*. SSAO and bloom are
available when the optional post-processing peers are installed.
Interaction and status. Click selection for modules, movers, whole tracks
and static custom assets, tinting the GLB itself rather than overlaying
wireframes; draggable translate / rotate / scale handles on a selected track
or custom asset, in the same palette as the orientation gizmo; drive-status blink plus
camera-facing warning and error icons; feed-segment (Einspeisestrang) tinting
that wraps the seam of a closed loop; stations, areas, dimensions and info bars;
stop-position ghost movers; and a stator heatmap driven by
(positionMm, value) samples.
Measuring and annotating. CAD-style tools — point-to-point distance with
optional ΔX/ΔY/ΔZ guides, angle, polyline, and distance along the XTS path —
picked in the scene with snapping to geometry corners and edges as well as to
mover centres, module boundaries, station stops and the track path itself.
Annotations pin a label anywhere, including to a mover, where they follow it
while it drives. Everything works the same in the 2D plan view, renders into
screenshots and video frames, and is fully drivable from
viewerRef.current.measurements.
Analysis. Sub-millimetre mover collision detection, as a one-shot call or a continuous monitor, plus module-level collision probes.
Export. exportScreenshot() renders offscreen at any resolution with MSAA,
in current-camera, top-down or saved-camera mode. beginFrameCapture() opens a
reusable session whose grab() returns a frame synchronously without image
encoding, and keeps producing frames while the window is minimised. Both bypass
the post-processing chain; the result reports whether that happened.
Camera. Orthographic top-down projection for a live 2D plan view, an
animated focusOn() for stations, areas, movers, modules or the whole scene,
and an opt-in X / Y / Z orientation gizmo.
Measurements and annotations
const viewer = useRef<XtsViewer3DRef>(null);
const [tool, setTool] = useState<MeasurementTool>('none');
<XtsViewer3D
ref={viewer}
config={config}
// The same tools work in the 3D view and the 2D plan view.
projection={plan2D ? 'orthographic' : 'perspective'}
measurement={{
tool, // 'distance' | 'angle' | 'path' | 'trackDistance' | 'annotation'
snap: { enabled: true, radiusPx: 14 },
onMeasurementCreate: (m, result) => console.log(m.kind, result.text),
}}
/>;
// …or place and read measurements without any user interaction:
viewer.current.measurements.add({
kind: 'distance',
points: [{ positionMm: [0, 0, 200] }, { positionMm: [1000, 0, 200] }],
});
viewer.current.measurements.results(); // [{ value: 1000, text: '1 000.0 mm', … }]
const saved = viewer.current.measurements.toJSON(); // persist, restore with fromJSON()See Measurements + annotations for the tools, the snap kinds and the full API.
Gallery
| | |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
A packaging line and a buffer loop side by side, placed with trackTransform. |
Station tubes with their stop markers, ghost movers parked on the stops, and cleanroom / manual-access zones. |
|
Dimensions along the track, stepped out from the modules they measure. |
Vertex-colour gradient along the centerline, fed from (positionMm, value) samples. |
|
Two movers of the line parked at a 1 mm overlap — checkMoverCollisions() reports the penetration. |
Emissive tint on the GLB plus camera-facing warning and error icons, on modules and movers alike. |
| 
feedSegmentHighlights tints whole electrical strands; the pink one wraps the loop seam. |
A distance across the loop with ΔX/ΔY guides and two annotations, placed through viewerRef.current.measurements. |
|
Selection tints the GLB itself — two modules green, one mover orange — rather than overlaying a wireframe. |
Opt-in PCF-soft shadows over the procedurally built indoor environment. |
|
750 movers on three ovals, animated at 60 Hz with no React commits in steady state. | |

exportScreenshot({ mode: 'top-down' }) — orthographic, fitted to the scene
AABB. The same view is available live through projection="orthographic".
Troubleshooting
Modules render as yellow boxes and movers as blue boxes
The viewer is showing wireframe placeholders because no GLB was accepted. The
console also reports THREE.WARNING: Multiple instances of Three.js being
imported, and no models/*.glb requests appear in the network panel.
This happens when a transitive dependency pins its own copy of three. Two
copies mean two THREE.* namespaces, and the instanceof checks inside
useGLTF reject every parsed scene across that boundary.
Deduplicate three in your bundler:
// vite.config.ts
export default defineConfig({
plugins: [react()],
resolve: { dedupe: ['three'] },
});For Webpack and Next.js, alias three to your root node_modules/three.
Bundler configuration
has the full snippets and explains how to clear Vite's pre-bundle cache
afterwards.
Documentation
- Using the component — every prop, every ref method, asset hosting, recipes and performance tuning.
- Adding a module — taking a new module, mover or tool type from STP file to calibrated GLB.
- Performance — the asset compression pipeline and the runtime budget.
- Releasing — the automated publish flow.
- Migrating to v6 — what changed and what to do about it.
Contributing
Setup, the asset pipeline, the repository layout and the release flow are in CONTRIBUTING.md.
License
MIT — see LICENSE.
