# Cable tray run

Ladder-type cable tray run after IEC 61537: 1 to 6 stacked trays (C-section side rails, rungs at 300 mm, splice plates at 3 m, copper earth bonding jumpers, laid cables at a set fill) on floor-standing 41 × 41 strut posts with gusseted cantilever arms, base plates and anchors, floor braces and an optional top cover. Run along X centred on the origin; posts on y = 0 on the −Z side; the lowest rungs at y = elevation, tiers every tierSpacing.

**Standards:** IEC 61537, NEMA VE 1, NEMA VE 2

## Installation

```bash
npx -y https://decktwin.com/cli/deck-twin-0.2.2.tgz add cable-tray
```

Copies `structure/cable-tray.tsx`, any helper files it imports and the shared `lib/` into `src/components/deck-twin/`. Needs `three`, `@react-three/fiber` and `@react-three/drei`.

## Usage

```tsx
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
import { Stage } from './components/deck-twin/lib/stage'
import { CableTray } from './components/deck-twin/structure/cable-tray'

export default function Scene() {
  return (
    <Canvas shadows camera={{ position: [8, 5, 9], fov: 35 }}>
      <Stage>
        <CableTray />
      </Stage>
      <OrbitControls makeDefault />
    </Canvas>
  )
}
```

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `length` | `number (m)` | `6` | 1 – 30 |
| `width` | `0.15 \| 0.3 \| 0.45 \| 0.6 \| 0.75 \| 0.9` | `0.6` |  |
| `tiers` | `number` | `3` | 1 – 6 |
| `tierSpacing` | `number (m)` | `0.3` | 0.2 – 0.6 |
| `elevation` | `number (m)` | `0.6` | 0.2 – 4 |
| `supportSpacing` | `number (m)` | `2` | 1 – 3 |
| `fill` | `number` | `0.6` | 0 – 1 |
| `cover` | `boolean` | `false` |  |
| `material` | `"galvanised" \| "stainless"` | `"galvanised"` |  |
| `...group` | position, rotation, scale… |  |  |

## P&ID

Cross-section: stacked open trays (U-channels) on cantilever arms from a post; on cable routing plans two parallel lines with the tray width and level (e.g. 600 W, TOT EL +3.2) and short diagonals or rung marks.

Electrical layout convention; IEC 61537

Only on electrical and instrument layouts. Two parallel lines with rungs in plan can be confused with a ladder; trays run along routes and carry width notes.

Cable tray schedule (IEC 61537 / NEMA VE 1) fields and the props they set:

| Field | Prop |
| --- | --- |
| Run length | `length` |
| Tray width | `width` |
| Number of tiers | `tiers` |
| Tier spacing | `tierSpacing` |
| Elevation | `elevation` |
| Support spacing | `supportSpacing` |
| Cover | `cover` |
| Material | `material` |

## Typical suppliers

Niedax. The geometry is generic and sized from public standards. It is not vendor CAD.

## Source

`structure/cable-tray.tsx`. This is the component file only: it imports shared code, so install it with the command above rather than pasting it alone. https://decktwin.com/r/cable-tray.json has every file.

```tsx
// Ladder-type cable tray run (IEC 61537) on floor-standing strut supports: multi-tier trays at 300 mm, cantilever arms, laid cables, splice plates, earth bonding and optional covers.
import type { Kit, MatKey } from '../lib/kit'
import { KitMesh, useKit, type GroupProps } from '../lib/mesh'
import { anchorBolt, boxMM } from './_steel'
import { ladderTray } from './_tray'

export interface CableTrayProps {
  /** along X, m */
  length?: number
  /** tray width, m */
  width?: number
  tiers?: number
  /** tier spacing, m */
  tierSpacing?: number
  /** rung level of the lowest tier above y = 0, m */
  elevation?: number
  /** support spacing, m */
  supportSpacing?: number
  /** cable fill, 0…1 */
  fill?: number
  /** cover on the top tier */
  cover?: boolean
  material?: 'galvanised' | 'stainless'
}

export const defaults = {
  length: 6,
  width: 0.6,
  tiers: 3,
  tierSpacing: 0.3,
  elevation: 0.6,
  supportSpacing: 2,
  fill: 0.6,
  cover: false,
  material: 'galvanised' as 'galvanised' | 'stainless',
}

type P = typeof defaults

export function CableTray(props: CableTrayProps & GroupProps) {
  const { length, width, tiers, tierSpacing, elevation, supportSpacing, fill, cover, material, ...group } = { ...defaults, ...props }
  const data = useKit((k) => build(k, { length, width, tiers, tierSpacing, elevation, supportSpacing, fill, cover, material }), [length, width, tiers, tierSpacing, elevation, supportSpacing, fill, cover, material])
  return (
    <group {...group}>
      <KitMesh data={data} />
    </group>
  )
}

export default CableTray

const STRUT = 0.041 // 41 × 41 slotted strut channel

function build(k: Kit, p: P) {
  const mat: MatKey = p.material === 'stainless' ? 'stainless' : 'galvanised'
  const L = Math.max(1, p.length)
  const W = Math.max(0.1, p.width)
  const n = Math.max(1, Math.round(p.tiers))
  const sp = Math.max(0.2, p.tierSpacing)
  const ys = Array.from({ length: n }, (_, i) => p.elevation + i * sp)
  const top = ys[n - 1] + 0.1
  // supports: a single back-to-back strut post at -Z with cantilever arms under each tier, braced to the floor
  const zp = -W / 2 - 0.06
  const m = Math.max(1, Math.ceil(L / Math.max(0.5, p.supportSpacing)))
  for (let i = 0; i <= m; i++) {
    const x = -L / 2 + 0.25 + ((L - 0.5) * i) / m
    k.box(mat, [0.16, 0.01, 0.16], [x, 0.005, zp])
    for (const a of [-1, 1]) anchorBolt(k, x + a * 0.055, 0.01, zp, 0.012)
    boxMM(k, mat, [x - STRUT, 0.01, zp - STRUT / 2], [x + STRUT, top + 0.15, zp + STRUT / 2])
    k.box('nut', [0.002, top + 0.12, 0.012], [x, (top + 0.15) / 2, zp + STRUT / 2 + 0.001]) // slot
    k.box(mat, [0.09, 0.006, 0.09], [x, top + 0.153, zp])
    for (const y of ys) {
      // cantilever arm with a gusseted root bracket
      boxMM(k, mat, [x - STRUT / 2, y - STRUT - 0.004, zp + STRUT / 2], [x + STRUT / 2, y - 0.004, zp + W + 0.12])
      k.box(mat, [0.008, 0.12, 0.1], [x, y - 0.06, zp + STRUT / 2 + 0.05])
      k.hex('nut', 0.018, 0.009, [x + STRUT / 2 + 0.005, y - 0.03, zp + STRUT / 2 + 0.03], 'x')
    }
    // floor brace on the open side
    if (n > 1 || p.elevation > 0.8) {
      const dx = Math.min(0.5, (L - 0.5) / m / 2)
      const s = i === m ? -1 : 1
      k.rod(mat, [x + s * dx, 0.02, zp], [x + s * STRUT, Math.min(ys[0] - 0.1, 0.9), zp], 0.012, 6)
    }
  }
  for (const y of ys) ladderTray(k, -L / 2, L / 2, zp + STRUT / 2 + 0.06 + W / 2, y, W, { fill: p.fill, mat: p.material === 'stainless' ? 'stainless' : 'cableTray' })
  const zc = zp + STRUT / 2 + 0.06 + W / 2
  // earth bonding jumpers across the splices on each tier
  for (const y of ys)
    for (let x = -L / 2 + 3; x < L / 2 - 0.2; x += 3) k.tube('copper', [[x - 0.12, y + 0.05, zc - W / 2 - 0.006], [x, y + 0.02, zc - W / 2 - 0.02], [x + 0.12, y + 0.05, zc - W / 2 - 0.006]], 0.005, 6, 0.03)
  if (p.cover) {
    const yc = ys[n - 1] + 0.1 + 0.004
    k.box(mat, [L, 0.0015, W + 0.03], [0, yc, zc])
    for (const s of [-1, 1]) k.box(mat, [L, 0.025, 0.0015], [0, yc - 0.012, zc + s * (W / 2 + 0.015)])
  }
}
```
