# Needle valve (instrument)

Small-bore instrument needle valve: forged stainless bar-stock body with integral RF flanges, union bonnet, panel nut, packing nut, rising stem and T-bar handle with colour cap, bleed plug and stamped tag. `open` lifts the needle stem. NPS 1/2 to 1.

**Standards:** ASME B16.34, MSS SP-99, ASME B16.5

## Installation

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

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

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

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `nps` | `0.5 \| 0.75 \| 1` | `0.5` |  |
| `rating` | `150 \| 300 \| 600 \| 900 \| 1500 \| 2500` | `600` |  |
| `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` | Inlet | -0.060, 0.000, 0.000 | -1, 0, 0 | 1/2" | 600 |
| `B` | Outlet | 0.060, 0.000, 0.000 | 1, 0, 0 | 1/2" | 600 |

## P&ID

Small bow-tie on an instrument, sample, vent or drain line (NPS 1 and below) with a stem ending in a pointed needle at the seat; often unlabelled, next to a gauge or transmitter.

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

Usually drawn smaller than line valves and attached to instrument tubing. Many P&IDs show instrument root and manifold valves only as a generic small gate symbol, or not at all; treat them as detail unless asked.

Instrument valve datasheet (MSS SP-99 / ASME B16.34) fields and the props they set:

| Field | Prop |
| --- | --- |
| Size | `nps` |
| Pressure class | `rating` |
| Tag number | `tag` |

## Typical suppliers

Parker Hannifin · Hoke. The geometry is generic and sized from public standards. It is not vendor CAD.

## Source

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

```tsx
// Instrument needle valve: forged bar-stock body with integral RF flanges, union bonnet, packing nut, rising stem and T-handle. ASME B16.34, MSS SP-99.
import type { Kit, MatKey } from '../lib/kit'
import { KitMesh, type GroupProps } from '../lib/mesh'
import { tagPlate } from '../lib/parts'
import { port, type Port } from '../lib/ports'
import { faceToFace, type Rating } from '../lib/standards'
import { bore, clamp, dims, endFlange, plateText, plug, revolveX, useKit2 } from './_body'

export interface NeedleValveProps {
  /** nominal pipe size, inches (1/2 … 1) */
  nps?: number
  rating?: Rating
  /** stem lift, 0 closed … 1 open */
  open?: number
  body?: MatKey
  tag?: string
}

export const defaults = { nps: 0.5, rating: 600 as Rating, open: 1, body: 'stainless' as MatKey, tag: '' }

const size = (nps: number) => clamp(nps, 0.5, 1)
/** typical: needle stem lift, m */
const lift = (nps: number) => 0.004 + 0.004 * size(nps)

export function ports(p: NeedleValveProps = {}): Port[] {
  const { nps, rating } = { ...defaults, ...p }
  const n = size(nps)
  const L = faceToFace('needle', n, rating)
  return [port('A', 'Inlet', [-L / 2, 0, 0], [-1, 0, 0], n, rating), port('B', 'Outlet', [L / 2, 0, 0], [1, 0, 0], n, rating)]
}

export function NeedleValve(props: NeedleValveProps & GroupProps) {
  const { nps, rating, open, body, tag, ...group } = { ...defaults, ...props }
  const n = size(nps)
  const [fixed, moving] = useKit2((k, mv) => build(k, mv, n, rating, body, tag), [n, rating, body, tag])
  return (
    <group {...group}>
      <KitMesh data={fixed} />
      <KitMesh data={moving} position-y={-(1 - clamp(open, 0, 1)) * lift(n)} />
    </group>
  )
}

export default NeedleValve

function build(k: Kit, mv: Kit, nps: number, rating: Rating, M: MatKey, tag: string) {
  const L = faceToFace('needle', nps, rating)
  const d = dims(nps, rating, L)
  const { D, f, xf } = d
  const rb = Math.max(0.004, D * 0.22)
  const rn = Math.max(D / 2 * 1.05, (f.hubDia / 2) * 0.85)
  for (const s of [-1, 1] as const) endFlange(k, d, s, rn, rb, M)
  // bar-stock block with necks to the flanges
  const b = clamp(0.032 + D * 0.5, 0.042, 0.07)
  revolveX(k, M, rb, [[rn, -xf - 0.003], [rn, -b * 0.45]], 24)
  revolveX(k, M, rb, [[rn, b * 0.45], [rn, xf + 0.003]], 24)
  k.rbox(M, [b, b * 1.05, b * 0.9], b * 0.06, [0, 0, 0])
  bore(k, rb, -L / 2, -b * 0.3)
  bore(k, rb, b * 0.3, L / 2)
  for (const s of [-1, 1]) k.disc('pipeBore', rb, [s * b * 0.3, 0, 0], s > 0 ? 'x' : '-x', 16)
  // body bleed plug, mounting holes, stamped tag
  plug(k, M, [0, -b * 0.2, b * 0.45], 'z', b * 0.16)
  for (const s of [-1, 1]) k.disc('pipeBore', b * 0.05, [s * b * 0.3, -b * 0.525 - 0.0005, 0], '-y', 10)
  tagPlate(k, tag || plateText(nps, rating), [0, b * 0.25, -b * 0.45 - 0.001], '-z', b * 0.9)
  // union bonnet (hex), bonnet body, panel nut, packing nut
  const y0 = b * 0.525
  k.hex(M, b * 0.82, b * 0.28, [0, y0 + b * 0.14, 0])
  k.cyl(M, b * 0.26, b * 0.5, [0, y0 + b * 0.28 + b * 0.25, 0], 'y', 20)
  k.hex(M, b * 0.62, b * 0.12, [0, y0 + b * 0.36, 0])
  k.hex('brass', b * 0.44, b * 0.18, [0, y0 + b * 0.78 + b * 0.09, 0])
  const yp = y0 + b * 0.96
  k.cyl('stainless', b * 0.035, b * 0.22, [b * 0.4, y0 + b * 0.14, 0], 'x', 8)
  // stem, T-handle and lock screw (moving, drawn open)
  const sr = b * 0.075
  mv.cyl(M, sr, b * 0.55, [0, yp + b * 0.2, 0], 'y', 12)
  const yh = yp + b * 0.48
  mv.cyl(M, sr * 1.8, b * 0.16, [0, yh, 0], 'y', 12)
  mv.cyl('handwheel', b * 0.055, b * 1.6, [0, yh, 0], 'x', 12)
  for (const s of [-1, 1]) mv.sphere('handwheel', b * 0.085, [s * b * 0.82, yh, 0], undefined, 12)
  mv.cyl('nut', sr * 0.5, sr * 2, [0, yh, b * 0.08], 'z', 6)
  mv.cyl('actuatorBlue', sr * 1.6, b * 0.03, [0, yh + b * 0.08 + 0.001, 0], 'y', 12)
}
```
