{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gasket",
  "type": "registry:component",
  "title": "Gasket",
  "description": "Flange gasket sized for the NPS and class. Spiral-wound per ASME B16.20 with stainless inner ring, alternating metal and filler winding, and a centring ring sized to the bolt circle with the winding colour on its rim and filler stripes. Ring-type joint in octagonal or oval section on the R-ring pitch diameter. B16.21 compressed-fibre sheet ring inside the bolts, with an identification tab. Open through the bore; plane of the gasket is YZ.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "piping/gasket.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/piping/gasket.tsx",
      "content": "// Flange gasket: ASME B16.20 spiral-wound (inner ring, winding, colour-coded centring ring), B16.20 / API 6A ring-type joint (oval or octagonal) or B16.21 non-metallic sheet ring.\nimport * as THREE from 'three'\nimport type { Kit, MatKey } from '../lib/kit'\nimport { paint } from '../lib/materials'\nimport { KitMesh, useKit, type GroupProps } from '../lib/mesh'\nimport { flange as flangeDims, inch, nearest, pipeOD, tableKeys, type Rating } from '../lib/standards'\nimport { textMaterial } from '../lib/textures'\n\nexport type GasketType = 'spiral-wound' | 'rtj' | 'sheet'\n\nexport interface GasketProps {\n  nps?: number\n  rating?: Rating\n  type?: GasketType\n  /** spiral-wound winding metal (B16.20 rim colour) */\n  winding?: '316L' | '304' | '321' | 'alloy-625' | 'monel'\n  /** spiral-wound filler (rim stripes) */\n  filler?: 'graphite' | 'ptfe' | 'mica'\n  /** RTJ ring cross-section */\n  ring?: 'octagonal' | 'oval'\n  /** sheet thickness, m (1.5 or 3 mm) */\n  thickness?: number\n}\n\nexport const defaults = { nps: 6, rating: 300 as Rating, type: 'spiral-wound' as GasketType, winding: '316L' as NonNullable<GasketProps['winding']>, filler: 'graphite' as NonNullable<GasketProps['filler']>, ring: 'octagonal' as 'octagonal' | 'oval', thickness: 0.003 }\n\nconst WINDING: Record<NonNullable<GasketProps['winding']>, string> = { '316L': '#2f8f46', '304': '#e3c21a', '321': '#2bb3b1', 'alloy-625': '#c9a227', monel: '#e07a1f' }\nconst FILLER: Record<NonNullable<GasketProps['filler']>, string> = { graphite: '#5a5d60', ptfe: '#f2f2f2', mica: '#7fc97f' }\n\n// approx: R-ring pitch diameter (inches) for class 300 / 600 by NPS; higher classes use the next ring up\nconst RTJ_P: Record<number, number> = { 0.5: 1.344, 0.75: 1.688, 1: 2, 1.5: 2.688, 2: 3.25, 3: 4.875, 4: 5.875, 6: 8.313, 8: 10.625, 10: 12.75, 12: 15, 14: 16.5, 16: 18.5, 18: 21, 20: 23, 24: 27.25 }\n\nexport function Gasket(props: GasketProps & GroupProps) {\n  const { nps, rating, type, winding, filler, ring, thickness, ...group } = { ...defaults, ...props }\n  const data = useKit((k) => build(k, nps, rating, type, winding, filler, ring, thickness), [nps, rating, type, winding, filler, ring, thickness])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n    </group>\n  )\n}\n\nexport default Gasket\n\n// annulus of radii r0..r1 and thickness t centred on x = 0, axis X\nconst annulus = (k: Kit, mat: MatKey, r0: number, r1: number, t: number, seg = 64) =>\n  k.lathe(mat, [[r1, -t / 2], [r1, t / 2], [r0, t / 2], [r0, -t / 2], [r1, -t / 2]], [0, 0, 0], 'x', seg)\n\nfunction build(k: Kit, nps: number, rating: Rating, type: GasketType, winding: NonNullable<GasketProps['winding']>, filler: NonNullable<GasketProps['filler']>, ring: 'octagonal' | 'oval', thickness: number) {\n  const f = flangeDims(nps, rating)\n  const ro = pipeOD(nps) / 2\n  if (type === 'sheet') {\n    // B16.21 ring gasket inside the bolts: ID = pipe OD, OD = bolt circle - bolt diameter\n    const r1 = f.boltCircle / 2 - f.boltDia / 2\n    annulus(k, paint('#3f4a3c', { metalness: 0, roughness: 0.85 }), ro, r1, thickness)\n    tab(k, r1, thickness, `${nps}\" CL${rating} ${(thickness * 1000).toFixed(1)}MM`, '#3f4a3c')\n    return\n  }\n  if (type === 'rtj') {\n    const P = inch(RTJ_P[nearest(tableKeys(RTJ_P), nps)] * (rating >= 900 ? 1.04 : 1))\n    const A = inch(nps <= 1 ? 0.313 : rating <= 600 ? 0.438 : rating <= 1500 ? (nps >= 8 ? 0.625 : 0.5) : 0.75)\n    const H = A * (ring === 'oval' ? 1.57 : 1.44)\n    const r = P / 2\n    const prof: [number, number][] = []\n    if (ring === 'oval') {\n      for (let i = 0; i <= 32; i++) {\n        const a = -Math.PI / 2 + (i / 32) * Math.PI * 2\n        prof.push([r + (A / 2) * Math.cos(a), (H / 2) * Math.sin(a)])\n      }\n    } else {\n      // octagon: flat C = A - 2·(H/2 - flat height)·tan 23°, flanks at 23°\n      const c = A * 0.7\n      const e = (A - c) / 2 / Math.tan((23 * Math.PI) / 180) / 2\n      prof.push([r + A / 2, -H / 2 + e], [r + A / 2, H / 2 - e], [r + c / 2, H / 2], [r - c / 2, H / 2], [r - A / 2, H / 2 - e], [r - A / 2, -H / 2 + e], [r - c / 2, -H / 2], [r + c / 2, -H / 2], [r + A / 2, -H / 2 + e])\n    }\n    k.lathe('machined', prof, [0, 0, 0], 'x', 96)\n    return\n  }\n  // spiral-wound: inner ring, winding (alternate metal / filler plies), centring ring with colour-coded rim\n  const t = 0.0032\n  const g = 0.0045\n  const wo = f.raisedFace / 2 - inch(0.125)\n  const wi = ro + inch(0.25)\n  const ri = Math.max(ro - inch(0.16), ro * 0.9)\n  const co = f.boltCircle / 2 - f.boltDia / 2\n  annulus(k, 'stainless', ri, wi, t)\n  const n = Math.max(6, Math.min(16, Math.round((wo - wi) / 0.0025)))\n  for (let i = 0; i < n; i++) {\n    const r0 = wi + ((wo - wi) * i) / n\n    const r1 = wi + ((wo - wi) * (i + 1)) / n\n    annulus(k, i % 2 ? paint(FILLER[filler], { metalness: 0.2, roughness: 0.8 }) : 'stainless', r0, r1, g - (i % 2 ? 0.0003 : 0))\n  }\n  annulus(k, 'galvanised', wo, co, t)\n  k.cyl(paint(WINDING[winding]), co + 0.0004, t * 0.9, [0, 0, 0], 'x', 64, undefined, true)\n  for (let i = 0; i < 4; i++) {\n    const a = (i / 4) * Math.PI * 2 + 0.4\n    const q = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), a)\n    const p = new THREE.Vector3(0, co + 0.0008, 0).applyQuaternion(q)\n    k.box(paint(FILLER[filler]), [t * 0.95, 0.001, 0.012], [p.x, p.y, p.z], q)\n  }\n  // stamped marking on the centring ring face\n  const label = textMaterial(`${nps}\" CL${rating} ${winding} ${filler.toUpperCase()} B16.20`, '#b4b9bc', '#2a2d30', 512, 64)\n  const w = Math.min(0.14, (co - wo) * 4)\n  k.plane(label, w, Math.min(co - wo, 0.02) * 0.7, [t / 2 + 0.0002, (co + wo) / 2, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2))\n}\n\n/** Printed identification tab outside the sealing face. */\nfunction tab(k: Kit, r1: number, t: number, text: string, color: string) {\n  k.box(paint(color, { metalness: 0, roughness: 0.85 }), [t, 0.05, 0.035], [0, r1 + 0.022, 0])\n  k.plane(textMaterial(text, color, '#f2f2f2', 256, 64), 0.03, 0.045, [t / 2 + 0.0003, r1 + 0.024, 0], new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI / 2, Math.PI / 2)))\n}\n"
    }
  ],
  "categories": [
    "piping"
  ],
  "meta": {
    "export": "Gasket",
    "anchor": "fitting",
    "standards": [
      "ASME B16.20",
      "ASME B16.21",
      "API 6A",
      "ASME B16.5"
    ]
  }
}