# Reducer

Butt-welding concentric or eccentric reducer (flat on top or bottom) with a smooth cone between short straight ends. End-to-end length from ASME B16.9 for the large end; B16.25 weld bevels or welded weld-neck flanges. Port A is the large end on the axis; for eccentric reducers port B is offset by the radius difference.

**Standards:** ASME B16.9, ASME B16.25, ASME B16.5

## Installation

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

Copies `piping/reducer.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 { Reducer } from './components/deck-twin/piping/reducer'

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

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `nps` | `number (NPS, in)` | `6` |  |
| `smallNps` | `number (NPS, in)` | `4` |  |
| `schedule` | `"10" \| "20" \| "40" \| "STD" \| "80" \| "XS" \| "160" \| "XXS" \| "10S" \| "40S" \| "80S"` | `"40"` |  |
| `kind` | `"concentric" \| "eccentric"` | `"concentric"` |  |
| `flat` | `"top" \| "bottom"` | `"top"` |  |
| `ends` | `"butt-weld" \| "flanged"` | `"butt-weld"` |  |
| `rating` | `150 \| 300 \| 600 \| 900 \| 1500 \| 2500` | `300` |  |
| `service` | `"none" \| "oil" \| "gas" \| "multiphase" \| "water" \| "seawater" \| "firewater" \| "steam" \| "air" \| "nitrogen" \| "chemical" \| "flare" \| "drain" \| "fuel-gas" \| "diesel" \| "potable" \| "cooling"` | `"none"` |  |
| `...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` | Large end | -0.070, 0.000, 0.000 | -1, 0, 0 | 6" | – |
| `B` | Small end | 0.070, 0.000, 0.000 | 1, 0, 0 | 4" | – |

## P&ID

A trapezoid in the line, the large end facing the larger pipe, with the two sizes written beside it (e.g. 8"×6"). Eccentric reducers have one flat side, noted FOT (flat on top) or FOB (flat on bottom).

ISO 10628-2 / PIP PIC001 (piping)

A size change at a valve or pump nozzle is often shown only by the size note. Eccentric FOT reducers sit on pump suctions.

Piping class / isometric bill of materials (ASME B16.9) fields and the props they set:

| Field | Prop |
| --- | --- |
| Large end size | `nps` |
| Small end size | `smallNps` |
| Wall schedule | `schedule` |
| Type (concentric / eccentric) | `kind` |
| Flat side | `flat` |
| End connection | `ends` |
| Flange rating | `rating` |
| Service code | `service` |

## Typical suppliers

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

## Source

`piping/reducer.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/reducer.json has every file.

```tsx
// Butt-welding concentric or eccentric reducer, ASME B16.9 end-to-end length, B16.25 bevels or welded WN flanges.
import type { Kit, MatKey, V3 } from '../lib/kit'
import { KitMesh, useKit, type GroupProps } from '../lib/mesh'
import { port, type Port } from '../lib/ports'
import { NPS_SIZES, pipeOD, pipeWall, reducer, type Rating, type Schedule, type Service } from '../lib/standards'
import { SEG, bevelDepth, buttWeld, flangeLength, gridSurface, portFlange, straight } from './_piping'

export interface ReducerProps {
  /** large end, inches */
  nps?: number
  /** small end, inches (falls back to the next size down if not smaller than nps) */
  smallNps?: number
  schedule?: Schedule
  kind?: 'concentric' | 'eccentric'
  /** eccentric only: the flat side (flat on top keeps vapour out of pump suctions) */
  flat?: 'top' | 'bottom'
  ends?: 'butt-weld' | 'flanged'
  rating?: Rating
  /** firewater paints the fitting red */
  service?: Service | 'none'
}

export const defaults = {
  nps: 6,
  smallNps: 4,
  schedule: '40' as Schedule,
  kind: 'concentric' as 'concentric' | 'eccentric',
  flat: 'top' as 'top' | 'bottom',
  ends: 'butt-weld' as 'butt-weld' | 'flanged',
  rating: 300 as Rating,
  service: 'none' as Service | 'none',
}

type P = typeof defaults

function geom(p: P) {
  const small = p.smallNps < p.nps ? p.smallNps : (NPS_SIZES.filter((s) => s < p.nps).pop() ?? p.nps)
  const H = reducer(p.nps, small)
  const rL = pipeOD(p.nps) / 2
  const rS = pipeOD(small) / 2
  const ecc = p.kind === 'eccentric' ? (p.flat === 'bottom' ? -(rL - rS) : rL - rS) : 0
  const flanged = p.ends === 'flanged'
  return { small, H, rL, rS, ecc, flL: flanged ? flangeLength(p.nps, p.rating) : 0, flS: flanged ? flangeLength(small, p.rating) : 0 }
}

export function ports(props: ReducerProps = {}): Port[] {
  const p = { ...defaults, ...props } as P
  const g = geom(p)
  const r = p.ends === 'flanged' ? p.rating : undefined
  return [port('A', 'Large end', [-g.H / 2 - g.flL, 0, 0], [-1, 0, 0], p.nps, r), port('B', 'Small end', [g.H / 2 + g.flS, g.ecc, 0], [1, 0, 0], g.small, r)]
}

export function Reducer(props: ReducerProps & GroupProps) {
  const { nps, smallNps, schedule, kind, flat, ends, rating, service, ...group } = { ...defaults, ...props }
  const p = { nps, smallNps, schedule, kind, flat, ends, rating, service } as P
  const data = useKit((k) => build(k, p), Object.values(p))
  return (
    <group {...group}>
      <KitMesh data={data} />
    </group>
  )
}

export default Reducer

function build(k: Kit, p: P) {
  const g = geom(p)
  const mat: MatKey = p.service === 'firewater' ? 'firewaterRed' : 'pipePaint'
  const flanged = p.ends === 'flanged'
  const { H, rL, rS } = g
  const tL = pipeWall(p.nps, p.schedule)
  const tS = pipeWall(g.small, p.schedule)
  const hL = flanged ? 0 : bevelDepth(p.nps, p.schedule)
  const hS = flanged ? 0 : bevelDepth(g.small, p.schedule)
  const e = H * 0.12
  // smooth cone between short straight ends; built along local +Y, then laid on +X (local x maps to world -y)
  const prof = (y: number) => {
    const t = Math.min(1, Math.max(0, (y - e) / (H - 2 * e)))
    return 0.5 - 0.5 * Math.cos(Math.PI * t)
  }
  const span = H - hL - hS
  const shell = (inner: boolean) => (s: number, f: number): V3 => {
    const y = hL + s * span
    const m = prof(y)
    const r = rL + (rS - rL) * m - (inner ? tL + (tS - tL) * m : 0)
    const yc = g.ecc === 0 ? 0 : Math.sign(g.ecc) * (rL - (rL + (rS - rL) * m))
    return [-yc + r * Math.sin(f), y, r * Math.cos(f)]
  }
  const nv = SEG(p.nps)
  gridSurface(k, mat, shell(false), 24, nv, [-H / 2, 0, 0], 'x')
  gridSurface(k, 'pipeBore', shell(true), 24, nv, [-H / 2, 0, 0], 'x', true)
  const B: V3 = [H / 2, g.ecc, 0]
  if (flanged) {
    portFlange(k, p.nps, p.rating, [-H / 2 - g.flL, 0, 0], [-1, 0, 0], { bore: rL - tL })
    portFlange(k, g.small, p.rating, [H / 2 + g.flS, g.ecc, 0], [1, 0, 0], { bore: rS - tS })
    buttWeld(k, p.nps, [-H / 2, 0, 0], [1, 0, 0])
    buttWeld(k, g.small, B, [1, 0, 0])
  } else {
    straight(k, mat, [-H / 2, 0, 0], [1, 0, 0], hL, p.nps, p.schedule, 'bevel', 'open')
    straight(k, mat, [H / 2 - hS, g.ecc, 0], [1, 0, 0], hS, g.small, p.schedule, 'open', 'bevel', nv)
  }
}
```
