{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fire-hydrant",
  "type": "registry:component",
  "title": "Pillar fire hydrant",
  "description": "Dry-barrel pillar hydrant on an underground firewater main. Below grade: flanged horizontal inlet (port inlet, hollow bore), long-radius shoe on a foot and concrete thrust block, buried riser with drain into a gravel pocket. At grade: bolted breakaway flange. Above: red barrel with casting bands and reflective marker, two 2½\" brass hose outlets left and right and an optional 4½\" pumper outlet to the front, each with a chained cap and pentagon nut, bolted bonnet, weather shield and pentagon operating nut. bury sets the inlet depth.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "safety/fire-hydrant.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/safety/fire-hydrant.tsx",
      "content": "// Pillar fire hydrant (dry barrel): flanged shoe on the buried firewater riser with thrust block and drain, breakaway flange at grade, red barrel with two 2½\" hose outlets and optional 4½\" pumper outlet, chained caps, bonnet with pentagon operating nut. AWWA C502, EN 14384, NFPA 24, ASME B16.5.\nimport * as THREE from 'three'\nimport { Kit, type V3 } from '../lib/kit'\nimport { paint } from '../lib/materials'\nimport { KitMesh, useKit, type GroupProps } from '../lib/mesh'\nimport { flange, flangedJoint, tagPlate } from '../lib/parts'\nimport { port, type Port } from '../lib/ports'\nimport { elbow90, pipeOD, pipeWall, type Rating } from '../lib/standards'\nimport { portBore } from './_valves'\n\nexport interface FireHydrantProps {\n  /** inlet (riser) size, inches */\n  nps?: number\n  rating?: Rating\n  /** 2: two hose outlets; 3: adds a pumper outlet on the front */\n  outlets?: number\n  /** inlet centreline below grade, m */\n  bury?: number\n  tag?: string\n}\n\nexport const defaults = { nps: 6, rating: 150 as Rating, outlets: 3, bury: 1.2, tag: 'FH-7101' }\n\nconst RED = 'firewaterRed'\nconst GRAVEL = paint('#7a766e', { metalness: 0, roughness: 0.95 })\nconst XI = -0.55 // inlet flange face, m\nconst HB = 0.8 // barrel top above grade, m\nconst YO = 0.55 // hose outlet centreline above grade, m\n\nexport function ports(p: FireHydrantProps = {}): Port[] {\n  const { nps, rating, bury } = { ...defaults, ...p }\n  return [port('inlet', 'Firewater inlet', [XI, -Math.max(0.6, bury), 0], [-1, 0, 0], nps, rating, 'utility')]\n}\n\nexport function FireHydrant(props: FireHydrantProps & GroupProps) {\n  const { nps, rating, outlets, bury, tag, ...group } = { ...defaults, ...props }\n  const b = Math.max(0.6, bury)\n  const data = useKit((k) => build(k, nps, rating, outlets >= 3, b, tag), [nps, rating, outlets, b, tag])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n    </group>\n  )\n}\n\nexport default FireHydrant\n\n/** Threaded outlet nozzle along `dir` from the barrel wall, with its cap, cap nut and chain back to a lug. */\nfunction outlet(k: Kit, y: number, dir: V3, r0: number, ro: number, L: number) {\n  const d = new THREE.Vector3(...dir)\n  const at = (s: number, dy = 0): V3 => [d.x * s, y + dy, d.z * s]\n  const ax = dir as V3\n  k.cyl(RED, ro * 1.25, 0.03, at(r0 + 0.005), ax, 28)\n  k.cyl('brass', ro, L - 0.03, at(r0 + 0.02 + (L - 0.03) / 2), ax, 28)\n  for (let i = 0; i < 5; i++) k.torus('brass', ro + 0.001, 0.0015, at(r0 + 0.035 + i * ((L - 0.05) / 5)), ax, 4, 28)\n  // cap: skirt over the thread, face, pentagon nut, chain lug\n  const zc = r0 + L - 0.005\n  k.cyl(RED, ro + 0.008, 0.045, at(zc), ax, 28)\n  k.cyl(RED, ro + 0.004, 0.008, at(zc + 0.026), ax, 28)\n  k.cyl(RED, ro * 0.38, 0.03, at(zc + 0.04), ax, 5)\n  for (const s of [-1, 1]) k.box(RED, [0.012, 0.03, 0.03], [d.x * zc + d.z * s * (ro + 0.012), y + 0.0, d.z * zc - d.x * s * (ro + 0.012)])\n  // chain from the cap lug down to a lug on the barrel\n  const a = new THREE.Vector3(d.x * zc, y - ro - 0.01, d.z * zc)\n  const bb = new THREE.Vector3(d.x * (r0 + 0.01), y - ro - 0.07, d.z * (r0 + 0.01))\n  k.box(RED, [0.02, 0.02, 0.02], [bb.x, bb.y + 0.005, bb.z])\n  const n = 7\n  for (let i = 0; i < n; i++) {\n    const t = (i + 0.5) / n\n    const p = a.clone().lerp(bb, t)\n    p.y -= Math.sin(t * Math.PI) * 0.03\n    const q = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), bb.clone().sub(a).normalize())\n    if (i % 2) q.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2))\n    k.torus('galvanised', 0.008, 0.0018, p.toArray() as V3, q, 4, 10)\n  }\n}\n\nfunction build(k: Kit, nps: number, rating: Rating, pumper: boolean, bury: number, tag: string) {\n  const D = pipeOD(nps)\n  const R = elbow90(nps)\n  const yi = -bury\n  // buried section: flanged inlet, duckfoot shoe on its thrust block, riser up to grade (bitumen coated)\n  const L = flange(k, nps, rating, [XI, yi, 0], '-x', true)\n  portBore(k, nps, rating, [XI, yi, 0], '-x')\n  k.pipe('castIron', [XI + L, yi, 0], [-R, yi, 0], D, pipeWall(nps, 'STD'))\n  k.bend('castIron', [-R, yi, 0], [1, 0, 0], [0, 1, 0], R, D)\n  const xf = -R * 0.85\n  k.cyl('castIron', D * 0.35, 0.12, [xf, yi - D / 2 - 0.055, 0], 'y', 20)\n  k.cyl('castIron', D * 0.5, 0.015, [xf, yi - D / 2 - 0.1125, 0], 'y', 20)\n  k.box('concrete', [0.5, 0.25, 0.5], [xf, yi - D / 2 - 0.12 - 0.125, 0])\n  // drain outlet at the foot of the riser into a gravel pocket\n  const yd = yi + R + 0.06\n  k.cyl('castIron', 0.014, 0.07, [D / 2 + 0.03, yd, 0], 'x', 10)\n  k.box(GRAVEL, [0.22, 0.14, 0.26], [D / 2 + 0.11, yd - 0.02, 0])\n  const yg = -0.06\n  k.pipe('castIron', [0, yi + R, 0], [0, yg - 0.03, 0], D, pipeWall(nps, 'STD'))\n  // breakaway (traffic) flange at grade\n  flangedJoint(k, nps, 150, [0, yg, 0], 'y')\n  // barrel: base collar, body with casting ribs, nozzle section, bonnet, weather shield, operating nut\n  const rb = D / 2 + 0.02\n  k.cyl(RED, rb, HB - 0.02, [0, 0.01 + (HB - 0.02) / 2, 0], 'y', 36)\n  k.cyl(RED, rb + 0.015, 0.05, [0, 0.03, 0], 'y', 36)\n  for (const y of [0.25, YO + 0.11]) k.cyl(RED, rb + 0.008, 0.02, [0, y, 0], 'y', 36)\n  k.cyl(RED, rb + 0.012, 0.04, [0, HB, 0], 'y', 36)\n  k.around(8, rb + 0.004, [0, HB + 0.022, 0], 'y', (p) => k.hex('nut', 0.018, 0.012, p, 'y'), Math.PI / 8)\n  k.dome(RED, rb, [0, HB + 0.02, 0], 'y', Math.PI / 2, 0.55, 36)\n  k.cyl(RED, rb * 0.45, 0.05, [0, HB + 0.02 + rb * 0.55, 0], 'y', 24)\n  k.lathe(RED, [[rb * 0.42, 0], [rb * 0.7, 0.0], [rb * 0.72, 0.008], [rb * 0.44, 0.03]], [0, HB + rb * 0.55 + 0.03, 0], 'y', 32)\n  k.cyl('castIron', 0.024, 0.03, [0, HB + rb * 0.55 + 0.075, 0], 'y', 5)\n  k.disc('nameplate', 0.02, [0, HB + rb * 0.55 + 0.0905, 0], 'y', 5)\n  // hose outlets left and right, pumper outlet to the front\n  outlet(k, YO, [1, 0, 0], rb - 0.005, 0.038, 0.11)\n  outlet(k, YO, [-1, 0, 0], rb - 0.005, 0.038, 0.11)\n  if (pumper) outlet(k, YO - 0.02, [0, 0, 1], rb - 0.005, 0.064, 0.12)\n  // red reflective marker band and tag on the back\n  k.cyl('nameplate', rb + 0.001, 0.03, [0, 0.4, 0], 'y', 36, undefined, true)\n  tagPlate(k, tag, [0, 0.2, -rb - 0.002], '-z', 0.1)\n}\n"
    },
    {
      "path": "safety/_valves.ts",
      "type": "registry:file",
      "target": "components/deck-twin/safety/_valves.ts",
      "content": "// Firewater isolation valve built into a Kit: flanged OS&Y gate valve (rising stem, handwheel, locked-open chain) along local X, stem up local Y.\nimport * as THREE from 'three'\nimport { Kit, axisVec, mat4, type Axis, type MatKey, type V3 } from '../lib/kit'\nimport { gasket } from '../lib/parts'\nimport { GASKET_SPIRAL_WOUND, faceToFace, flange as flangeDims, handwheelDia, pipeOD, stemHeight, type Rating } from '../lib/standards'\n\n/** OS&Y gate valve centred on the origin of the current Kit frame; ends at x = ±faceToFace/2. */\nexport function osyGate(k: Kit, nps: number, rating: Rating, body: MatKey = 'firewaterRed') {\n  const D = pipeOD(nps)\n  const f = flangeDims(nps, rating)\n  const L = faceToFace('gate', nps, rating)\n  const neck = (D / 2) * 1.12\n  for (const s of [-1, 1]) {\n    const x = (s * L) / 2\n    k.at([x, 0, 0], s > 0 ? 'x' : '-x', () => {\n      const yb = -f.raisedFaceHeight - f.thickness\n      k.lathe(body, [[neck, yb - 0.003], [neck + 0.003, yb], [f.od / 2, yb], [f.od / 2, -f.raisedFaceHeight], [f.raisedFace / 2, -f.raisedFaceHeight]], [0, 0, 0], 'y', 32)\n      k.lathe('machined', [[f.raisedFace / 2, -f.raisedFaceHeight], [f.raisedFace / 2, 0], [D / 2 * 0.85, 0]], [0, 0, 0], 'y', 32)\n    })\n  }\n  k.cyl(body, neck, L - 2 * (f.raisedFaceHeight + f.thickness) + 0.004, [0, 0, 0], 'x', 28)\n  const rx = Math.min(D * 0.55 + 0.012, L / 2 - f.raisedFaceHeight - f.thickness)\n  const rz = D * 0.66 + 0.014\n  const yTop = D * 0.85 + 0.02\n  const yBot = -D * 0.5\n  k.add(body, new THREE.CylinderGeometry(1, 1, yTop - yBot, 32), mat4([0, (yTop + yBot) / 2, 0], 'y', [rx, 1, rz]))\n  k.sphere(body, 1, [0, yBot, 0], [rx, D * 0.25, rz], 24)\n  // bonnet flange and studs\n  const t = Math.max(0.01, D * 0.11)\n  const bd = Math.max(0.01, Math.min(0.032, f.boltDia * 0.9))\n  const brx = rx + f.boltDia * 2.4\n  const brz = rz + f.boltDia * 2.4\n  k.add(body, new THREE.CylinderGeometry(1, 1, 2 * t, 32), mat4([0, yTop, 0], 'y', [brx, 1, brz]))\n  const n = nps >= 8 ? 12 : 8\n  for (let i = 0; i < n; i++) {\n    const a = ((i + 0.5) / n) * Math.PI * 2\n    const x = Math.cos(a) * (brx - bd * 1.6)\n    const z = Math.sin(a) * (brz - bd * 1.6)\n    k.cyl('boltXylan', bd / 2, 2 * t + bd * 2.2, [x, yTop, z], 'y', 6)\n    k.hex('nut', bd * 1.85, bd * 0.9, [x, yTop + t + bd * 0.45, z], 'y')\n  }\n  // bonnet, yoke, stem, handwheel\n  const H = stemHeight('gate', nps, rating)\n  const hB = Math.max(0.06, D * 1.0 + 0.03)\n  const stemR = Math.max(0.007, D * 0.085)\n  const y0 = yTop + t\n  k.add(body, new THREE.CylinderGeometry(1, 1, hB, 28), mat4([0, y0 + hB / 2, 0], 'y', [Math.max(stemR * 2.2, rx * 0.7), 1, Math.max(stemR * 2.2, rz * 0.62)]))\n  const yB = y0 + hB\n  const yT = Math.max(yB + 0.1, H * 0.78)\n  const legX = stemR * 2.4 + Math.max(0.02, D * 0.22)\n  const legW = Math.max(0.014, D * 0.16)\n  const legT = Math.max(0.01, D * 0.09)\n  k.cyl(body, stemR * 2.6, 0.02, [0, yB + 0.01, 0], 'y', 16)\n  for (const s of [-1, 1]) k.box(body, [legW, yT - yB, legT], [s * legX, (yB + yT) / 2, 0])\n  k.box(body, [legX * 2 + legW, legT * 1.6, legT * 2.2], [0, yT, 0])\n  k.cyl(body, stemR * 2.5, legT * 3, [0, yT, 0], 'y', 16)\n  k.cyl('stainless', stemR, H - yB, [0, (H + yB) / 2, 0], 'y', 12)\n  const hw = Math.min(0.75, handwheelDia('gate', nps, rating))\n  k.handwheel('handwheel', hw, [0, yT + legT * 1.5 + 0.01, 0], 'y', hw > 0.35 ? 6 : 4)\n  // locked-open chain and padlock through the rim\n  const r = hw / 2\n  const yh = yT + legT * 1.5 + 0.01\n  k.tube('galvanised', [[r * 0.7, yh, r * 0.7], [legX + legW, (yh + yB) / 2, legT], [legX + legW / 2, yB + 0.04, legT / 2]], 0.004, 4)\n  k.box('brass', [0.035, 0.04, 0.015], [legX + legW + 0.01, (yh + yB) / 2 - 0.03, legT])\n  return L\n}\n\n/** Gasket and stud bolts across two mating flanges whose faces meet at `pos` (flanges drawn separately). */\nexport function boltUp(k: Kit, nps: number, rating: Rating, pos: V3, axis: Axis) {\n  const f = flangeDims(nps, rating)\n  const v = axisVec(axis)\n  gasket(k, nps, rating, pos, axis)\n  const L = 2 * (f.thickness + f.raisedFaceHeight) + GASKET_SPIRAL_WOUND + f.boltDia * 3\n  const start = new THREE.Vector3(...pos).addScaledVector(v, -L / 2).toArray() as V3\n  k.boltCircle('boltXylan', 'nut', f.bolts, f.boltCircle / 2, start, axis, f.boltDia, L, [f.boltDia * 0.95, L - f.boltDia * 0.95])\n}\n\n/** Bore lining inside a port flange (lib flanges have no inner hub surface), face at `face`, `axis` pointing out. */\nexport function portBore(k: Kit, nps: number, rating: Rating, face: V3, axis: Axis) {\n  const f = flangeDims(nps, rating)\n  const ri = (pipeOD(nps) / 2) * 0.88\n  k.lathe('pipeBore', [[ri, 0.0005], [ri, -(f.hubLength + f.raisedFaceHeight + 0.01)]], face, axis, 24)\n}\n"
    }
  ],
  "categories": [
    "safety"
  ],
  "meta": {
    "export": "FireHydrant",
    "anchor": "base",
    "standards": [
      "AWWA C502",
      "EN 14384",
      "NFPA 24",
      "ASME B16.5"
    ]
  }
}