# Spectacle blind

Figure-8 spectacle blind for positive isolation: solid paddle and spacer ring on a tie bar, thickness from the ASME B16.48 pressure formula, between two weld-neck flanges with spiral-wound gaskets, lengthened studs and jack screws. state swaps the paddle and spacer; the idle end shows a red (blind) or green (spacer) flash.

**Standards:** ASME B16.48, ASME B16.5, ASME B16.20, ASME PCC-1

## Installation

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

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

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

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `nps` | `number (NPS, in)` | `6` |  |
| `rating` | `150 \| 300 \| 600 \| 900 \| 1500 \| 2500` | `300` |  |
| `state` | `"closed" \| "open"` | `"closed"` |  |
| `flanges` | `boolean` | `true` |  |
| `...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` | Face A | -0.009, 0.000, 0.000 | -1, 0, 0 | 6" | 300 |
| `B` | Face B | 0.009, 0.000, 0.000 | 1, 0, 0 | 6" | 300 |

## P&ID

A figure-8 drawn across the line between two flange bars: one circle filled (solid blind) and one open (spacer ring). The filled half on the line means the line is blinded (normally closed); the open half on the line means it is open.

tag SB · ISO 10628-2 / PIP PIC001 (piping)

Note NO / NC (or SO / SC) beside it for the normal position. Many legends draw the figure-8 beside the flange pair on a short stem instead of across the line: the circle next to the stem is the half in the line (open ring = open, blank = closed), and the blank may be hatched or drawn as concentric circles. A single filled paddle is a spade; a single open ring is a spacer.

Piping class / special item datasheet (ASME B16.48) fields and the props they set:

| Field | Prop |
| --- | --- |
| Nominal size | `nps` |
| Flange rating | `rating` |
| Normal position (NO / NC) | `state` |
| Include flanges | `flanges` |

## Typical suppliers

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

## Source

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

```tsx
// Figure-8 spectacle blind (ASME B16.48) between two weld-neck flanges, with spiral-wound gaskets, long studs and jack screws.
import * as THREE from 'three'
import type { Kit } from '../lib/kit'
import { KitMesh, useKit, type GroupProps } from '../lib/mesh'
import { port, type Port } from '../lib/ports'
import { flange as flangeDims, pipeOD, pipeWall, spectacleBlind, studLength, type Rating } from '../lib/standards'
import { textMaterial } from '../lib/textures'
import { GAP, jointStuds, portFlange, spiralWound } from './_piping'

export interface SpectacleBlindProps {
  nps?: number
  rating?: Rating
  /** closed: solid paddle in the line; open: spacer ring in the line */
  state?: 'open' | 'closed'
  /** draw the flanges, gaskets and studs either side (the plant assembler draws its own mating flanges at A and B) */
  flanges?: boolean
}

export const defaults = { nps: 6, rating: 300 as Rating, state: 'closed' as 'open' | 'closed', flanges: true }

// Ports A and B are the two faces of the plate.
export function ports(p: SpectacleBlindProps = {}): Port[] {
  const { nps, rating } = { ...defaults, ...p }
  const t = spectacleBlind(nps, rating).thickness
  return [port('A', 'Face A', [-t / 2, 0, 0], [-1, 0, 0], nps, rating), port('B', 'Face B', [t / 2, 0, 0], [1, 0, 0], nps, rating)]
}

export function SpectacleBlind({ nps = defaults.nps, rating = defaults.rating, state = defaults.state, flanges = defaults.flanges, ...group }: SpectacleBlindProps & GroupProps) {
  const data = useKit((k) => build(k, nps, rating, state, flanges), [nps, rating, state, flanges])
  return (
    <group {...group}>
      <KitMesh data={data} />
    </group>
  )
}

export default SpectacleBlind

function build(k: Kit, nps: number, rating: Rating, state: 'open' | 'closed', flanges: boolean) {
  const sb = spectacleBlind(nps, rating)
  const f = flangeDims(nps, rating)
  const t = sb.thickness
  const R = sb.od / 2
  const bore = pipeOD(nps) / 2 - pipeWall(nps, 'STD')
  const seg = nps >= 6 ? 48 : 32
  const paddle = (y: number) => k.cyl('flangeSteel', R, t, [0, y, 0], 'x', seg)
  const spacer = (y: number) => k.lathe('flangeSteel', [[bore, -t / 2], [R, -t / 2], [R, t / 2], [bore, t / 2], [bore, -t / 2]], [0, y, 0], 'x', seg)
  const yOut = sb.centres
  if (state === 'closed') {
    paddle(0)
    spacer(yOut)
  } else {
    spacer(0)
    paddle(yOut)
  }
  // tie bar between the two ends, with the stamped size / class / material marking on its edge
  const y0 = R * 0.85
  const y1 = yOut - R * 0.85
  k.box('flangeSteel', [t, y1 - y0, sb.webWidth], [0, (y0 + y1) / 2, 0])
  const label = `${nps}" CL${rating} ${state === 'closed' ? 'BLIND' : 'SPACER'}`
  k.plane(textMaterial(label, '#8e9498', '#1d2023', 512, 96), sb.webWidth * 0.95, Math.min(y1 - y0, sb.webWidth) * 0.22, [t / 2 + 0.0006, (y0 + y1) / 2, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2))
  // painted end flash: red on the blind paddle, green on the spacer, visible from outside the joint
  const flash = (y: number, solid: boolean) => k.box(solid ? 'actuatorRed' : 'safetyGreen', [t + 0.001, Math.min(0.04, R * 0.3), sb.webWidth * 0.8], [0, y + R - Math.min(0.02, R * 0.15), 0])
  flash(yOut, state === 'open')

  if (!flanges) return
  for (const s of [-1, 1]) {
    portFlange(k, nps, rating, [s * (t / 2 + GAP), 0, 0], [-s, 0, 0], { studs: false, bore })
    spiralWound(k, nps, rating, [s * (t / 2 + GAP / 2), 0, 0], [1, 0, 0])
  }
  jointStuds(k, nps, rating, [0, 0, 0], [1, 0, 0], studLength(nps, rating) + t + GAP)
  // two jack screws through flange A at the 3 and 9 o'clock positions, clear of the bolt pattern
  const gap = t + 2 * GAP + 2 * f.raisedFaceHeight
  const xa = -(gap / 2 + f.thickness)
  const d = f.boltDia * 0.8
  for (const z of [-1, 1]) {
    const zz = (z * f.boltCircle) / 2
    k.hex('nut', d * 1.75, d * 0.7, [xa - d * 0.35, 0, zz], 'x')
    k.cyl('galvanised', d / 2, f.thickness + gap, [xa + (f.thickness + gap) / 2, 0, zz], 'x', 10)
  }
}
```
