{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stud-bolt-set",
  "type": "registry:component",
  "title": "Stud bolt set",
  "description": "Complete stud-bolt set for one flanged joint: stud count, diameter and RF length from the ASME B16.5 bolting tables, threaded A193 B7 studs with chamfered ends and B7 end marks, two A194 2H heavy hex nuts each with washer face and 2H mark, set at a made-up joint grip so a few threads protrude. Coating: PTFE blue, hot-dip galvanised, black or zinc-nickel. Laid out on the bolt circle or in a row.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "piping/stud-bolt-set.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/piping/stud-bolt-set.tsx",
      "content": "// Flange stud-bolt set per ASME B16.5 bolting tables: ASTM A193 B7 threaded studs with chamfered ends, two A194 2H heavy hex nuts each, optional coating.\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 { GASKET_SPIRAL_WOUND, flange as flangeDims, studLength, type Rating } from '../lib/standards'\nimport { textMaterial } from '../lib/textures'\n\nexport type Coating = 'ptfe-blue' | 'hot-dip' | 'black' | 'zinc-nickel'\n\nexport interface StudBoltSetProps {\n  nps?: number\n  rating?: Rating\n  coating?: Coating\n  /** as installed on the bolt circle (joint), or laid out in a row */\n  layout?: 'joint' | 'row'\n}\n\nexport const defaults = { nps: 6, rating: 300 as Rating, coating: 'ptfe-blue' as Coating, layout: 'joint' as 'joint' | 'row' }\n\nconst COAT: Record<Coating, string> = { 'ptfe-blue': '#27427a', 'hot-dip': '#a9adb0', black: '#1f2124', 'zinc-nickel': '#c9cbb8' }\n\nexport function StudBoltSet({ nps = defaults.nps, rating = defaults.rating, coating = defaults.coating, layout = defaults.layout, ...group }: StudBoltSetProps & GroupProps) {\n  const data = useKit((k) => build(k, nps, rating, coating, layout), [nps, rating, coating, layout])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n    </group>\n  )\n}\n\nexport default StudBoltSet\n\n// UNC threads per inch by nominal diameter (8-UN from 1\")\nconst TPI: [number, number][] = [[0.5, 13], [0.625, 11], [0.75, 10], [0.875, 9]]\nconst pitch = (d: number) => 0.0254 / (TPI.find(([dia]) => d <= (dia + 0.06) * 0.0254)?.[1] ?? 8)\n\n/** One stud along +X centred at the origin: threaded body as a lathe with a real thread profile, 45° chamfered ends. */\nfunction stud(k: Kit, mat: MatKey, d: number, L: number) {\n  const p = pitch(d)\n  const r = d / 2\n  const rr = r - p * 0.54\n  const ch = p * 1.2\n  const prof: [number, number][] = [[0, -L / 2], [rr, -L / 2], [r, -L / 2 + ch]]\n  for (let y = -L / 2 + ch; y < L / 2 - ch - p; y += p) prof.push([r, y + p * 0.12], [rr, y + p * 0.5], [r, y + p * 0.88])\n  prof.push([r, L / 2 - ch], [rr, L / 2], [0, L / 2])\n  k.lathe(mat, prof, [0, 0, 0], 'x', 10)\n}\n\n/** A194 2H heavy hex nut: across flats 1.5d + 1/8\", height d, washer face against the flange, chamfered outer face with the grade mark. */\nfunction nut(k: Kit, mat: MatKey, d: number, x: number, out: 1 | -1) {\n  const af = 1.5 * d + 0.0032\n  const ac = af / Math.cos(Math.PI / 6)\n  k.hex(mat, ac * 0.985, d * 0.92, [x, 0, 0], 'x')\n  k.cyl(mat, af * 0.48, d * 0.05, [x - out * d * 0.485, 0, 0], 'x', 24)\n  k.cyl(mat, out > 0 ? af * 0.5 : af * 0.44, d * 0.03, [x + out * d * 0.475, 0, 0], 'x', 24, out > 0 ? af * 0.44 : af * 0.5)\n  k.plane(textMaterial('2H', COAT_TXT, '#f2f2f2', 64, 64), af * 0.24, af * 0.24, [x + out * (d * 0.492), af * 0.3, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), (out * Math.PI) / 2))\n}\nconst COAT_TXT = '#222831'\n\nfunction build(k: Kit, nps: number, rating: Rating, coating: Coating, layout: 'joint' | 'row') {\n  const f = flangeDims(nps, rating)\n  const d = f.boltDia\n  const L = studLength(nps, rating)\n  const mat: MatKey = coating === 'ptfe-blue' ? 'boltXylan' : paint(COAT[coating], { metalness: 0.6, roughness: 0.4, clearcoat: 0, weather: 0 })\n  // nut positions for a made-up RF joint: grip = two flanges + faces + gasket, nut height = d\n  const grip = 2 * (f.thickness + f.raisedFaceHeight) + GASKET_SPIRAL_WOUND\n  const xn = grip / 2 + d * 0.46\n  const place = (fn: () => void, pos: THREE.Vector3) => k.with(new THREE.Matrix4().makeTranslation(pos.x, pos.y, pos.z), fn)\n  const each = (pos: THREE.Vector3) =>\n    place(() => {\n      stud(k, mat, d, L)\n      nut(k, mat, d, -xn, -1)\n      nut(k, mat, d, xn, 1)\n      k.disc(textMaterial('B7', '#3a3f45', '#f2f2f2', 64, 64), d * 0.32, [L / 2 + 0.0003, 0, 0], 'x', 16)\n    }, pos)\n  for (let i = 0; i < f.bolts; i++) {\n    if (layout === 'row') each(new THREE.Vector3(0, d / 2 + d * 0.9, (i - (f.bolts - 1) / 2) * d * 3.2))\n    else {\n      const a = Math.PI / f.bolts + (i / f.bolts) * Math.PI * 2\n      each(new THREE.Vector3(0, -Math.sin(a) * (f.boltCircle / 2), Math.cos(a) * (f.boltCircle / 2)))\n    }\n  }\n}\n"
    }
  ],
  "categories": [
    "piping"
  ],
  "meta": {
    "export": "StudBoltSet",
    "anchor": "fitting",
    "standards": [
      "ASME B16.5",
      "ASME B18.2.2",
      "ASTM A193 B7",
      "ASTM A194 2H",
      "ASME PCC-1"
    ]
  }
}