# Ball valve

Flanged RF ball valve. Floating two-piece body with a wrench lever for small sizes, trunnion-mounted three-piece side-entry body with sealant fittings, trunnion cover, lifting lugs and a worm gear for larger sizes. Full or reduced bore; the ported ball is visible down the bore and turns with `open`.

**Standards:** API 6D, ISO 17292, ASME B16.34, ASME B16.10, ASME B16.5, ISO 5211

## Installation

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

Copies `valves/ball-valve.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 { BallValve } from './components/deck-twin/valves/ball-valve'

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

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `nps` | `number (NPS, in)` | `4` |  |
| `rating` | `150 \| 300 \| 600 \| 900 \| 1500 \| 2500` | `300` |  |
| `bore` | `"full" \| "reduced"` | `"full"` |  |
| `mounting` | `"auto" \| "floating" \| "trunnion"` | `"auto"` |  |
| `operator` | `"auto" \| "lever" \| "gear"` | `"auto"` |  |
| `open` | `number` | `1` | 0 – 1 |
| `tag` | `string` | `""` |  |
| `...group` | position, rotation, scale… |  |  |

## Ports

Flange faces in the component's local frame, metres. Origin: pipe centreline, flow along X.

| Id | Label | Position | Direction | Size | Class |
| --- | --- | --- | --- | --- | --- |
| `A` | End A | -0.152, 0.000, 0.000 | -1, 0, 0 | 4" | 300 |
| `B` | End B | 0.152, 0.000, 0.000 | 1, 0, 0 | 4" | 300 |

## P&ID

Bow-tie across the line with an open circle at the centre where the triangle points meet. Manual valves may show a stem with a T or a lever.

tag HV · ISO 10628-2 / ISO 14617-8 / PIP PIC001

Open circle = ball; filled dot = globe; plain bow-tie = gate. The same body under a square actuator with an S or a fail-close note is an ESDV / SDV / XV (esd-valve). Some legends write FB / RB beside it for full or reduced bore.

Ball valve datasheet (API 6D / ISO 17292) fields and the props they set:

| Field | Prop |
| --- | --- |
| Size | `nps` |
| Pressure class | `rating` |
| Bore | `bore` |
| Ball mounting | `mounting` |
| Operator | `operator` |
| Tag number | `tag` |

## Typical suppliers

Cameron (SLB) · Bray · Fluitek · Smith-Cooper (Sharpe) · Bola-Tek. The geometry is generic and sized from public standards. It is not vendor CAD.

## Source

`valves/ball-valve.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/ball-valve.json has every file.

```tsx
// Flanged ball valve: floating two-piece or trunnion three-piece side-entry body, full or reduced bore, lever or worm gear. API 6D / ISO 17292, ASME B16.10.
import { KitMesh, type GroupProps } from '../lib/mesh'
import type { MatKey } from '../lib/kit'
import { port, type Port } from '../lib/ports'
import { faceToFace, handwheelDia, type Rating } from '../lib/standards'
import { ballBody, clamp, classK, isoPadSize, plateText, useKit2 } from './_body'
import { isoPad, wormGear, wrenchLever } from './_operators'

export interface BallValveProps {
  /** nominal pipe size, inches */
  nps?: number
  rating?: Rating
  bore?: 'full' | 'reduced'
  /** auto: trunnion from NPS 6 (NPS 4 at Class 600+) */
  mounting?: 'auto' | 'floating' | 'trunnion'
  /** auto: lever on floating valves, gear on trunnion valves */
  operator?: 'auto' | 'lever' | 'gear'
  /** 0 closed … 1 open (quarter turn) */
  open?: number
  body?: MatKey
  tag?: string
}

export const defaults = {
  nps: 4,
  rating: 300 as Rating,
  bore: 'full' as 'full' | 'reduced',
  mounting: 'auto' as 'auto' | 'floating' | 'trunnion',
  operator: 'auto' as 'auto' | 'lever' | 'gear',
  open: 1,
  body: 'valveBody' as MatKey,
  tag: '',
}

const family = (bore: string) => (bore === 'reduced' ? 'ball-reduced' : 'ball-full')

export function ports(p: BallValveProps = {}): Port[] {
  const { nps, rating, bore } = { ...defaults, ...p }
  const L = faceToFace(family(bore), nps, rating)
  return [port('A', 'End A', [-L / 2, 0, 0], [-1, 0, 0], nps, rating), port('B', 'End B', [L / 2, 0, 0], [1, 0, 0], nps, rating)]
}

export function BallValve(props: BallValveProps & GroupProps) {
  const { nps, rating, bore, mounting, operator, open, body, tag, ...group } = { ...defaults, ...props }
  const trunnion = mounting === 'trunnion' || (mounting === 'auto' && (nps >= 6 || (nps >= 4 && rating >= 600)))
  const gear = operator === 'gear' || (operator === 'auto' && trunnion)
  const [fixed, moving] = useKit2(
    (k, mv) => {
      const L = faceToFace(family(bore), nps, rating)
      const b = ballBody(k, mv, { nps, rating, L, reduced: bore === 'reduced', trunnion, mat: body, text: tag || plateText(nps, rating, bore === 'reduced' ? 'RB' : 'FB') })
      const y = isoPad(k, body, b.yTop, isoPadSize(b.D))
      if (gear) {
        const g = clamp((0.08 + b.D * 0.7) * classK(rating), 0.14, 0.7)
        wormGear(k, mv, y, g, nps > 4 ? handwheelDia(family(bore), nps, rating) : 0.3, body, b.Rb + 0.12)
      } else wrenchLever(k, mv, y, b.stemR, handwheelDia(family(bore), nps, rating))
    },
    [nps, rating, bore, trunnion, gear, body, tag],
  )
  return (
    <group {...group}>
      <KitMesh data={fixed} />
      <KitMesh data={moving} rotation-y={-(1 - clamp(open, 0, 1)) * (Math.PI / 2)} />
    </group>
  )
}

export default BallValve
```
