{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "deluge-nozzle",
  "type": "registry:component",
  "title": "Deluge nozzle / sprinkler head",
  "description": "Water-spray discharge head on a short flanged drop (port A at the weld-neck flange, forged reducing coupling to a ½\" NPT head, tie-wired tag). kind mv is an open medium-velocity nozzle with a hollow orifice and a slotted external deflector on two frame arms; hv a high-velocity conical nozzle with an open orifice and swirl vane; sprinkler a pendent glass-bulb sprinkler whose bulb colour follows temperature; water-mist a stainless head with nine micro-nozzles. spray draws the discharge cone at angle. Use kind, not type (reserved by Object3D).",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "safety/deluge-nozzle.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/safety/deluge-nozzle.tsx",
      "content": "// Fire-protection discharge head on a short flanged drop: open medium-velocity spray nozzle (external deflector), high-velocity nozzle (open conical orifice), glass-bulb sprinkler (temperature-coded bulb) or multi-orifice water-mist head; optional spray cone. NFPA 15, NFPA 13, EN 12259-1, ISO 6182-1, ASME B16.5.\nimport * as THREE from 'three'\nimport { Kit, type MatKey } from '../lib/kit'\nimport { paint } from '../lib/materials'\nimport { KitMesh, useKit, type GroupProps } from '../lib/mesh'\nimport { flange, tagPlate } from '../lib/parts'\nimport { port, type Port } from '../lib/ports'\nimport { pipeOD, pipeWall, type Rating } from '../lib/standards'\nimport { portBore } from './_valves'\n\nexport type NozzleKind = 'mv' | 'hv' | 'sprinkler' | 'water-mist'\n\nexport interface DelugeNozzleProps {\n  /** mv: medium-velocity spray; hv: high-velocity; sprinkler: glass-bulb; water-mist: multi-orifice head */\n  kind?: NozzleKind\n  /** drop size, inches */\n  nps?: number\n  rating?: Rating\n  /** flange face above the nozzle body centre, m */\n  drop?: number\n  /** spray cone included angle, ° */\n  angle?: number\n  /** sprinkler bulb rating, °C (57 orange, 68 red, 79 yellow, 93 green, 141 blue) */\n  temperature?: number\n  spray?: boolean\n  tag?: string\n}\n\nexport const defaults = { kind: 'mv' as NozzleKind, nps: 1, rating: 150 as Rating, drop: 0.2, angle: 110, temperature: 68, spray: false, tag: 'DN-7201' }\n\nconst BULB: [number, string][] = [[57, '#ff8a1a'], [68, '#e8241a'], [79, '#f2d21a'], [93, '#2fb34a'], [141, '#2a6bff'], [182, '#8a3cc7']]\nconst WATER = paint('#bfe3f2', { transparent: true, opacity: 0.22, roughness: 0.2, metalness: 0, weather: 0 })\n\nexport function ports(p: DelugeNozzleProps = {}): Port[] {\n  const { nps, rating, drop } = { ...defaults, ...p }\n  return [port('A', 'Inlet', [0, Math.max(0.12, drop), 0], [0, 1, 0], nps, rating, 'utility')]\n}\n\nexport function DelugeNozzle(props: DelugeNozzleProps & GroupProps) {\n  const { kind, nps, rating, drop, angle, temperature, spray, tag, ...group } = { ...defaults, ...props }\n  const L = Math.max(0.12, drop)\n  const data = useKit((k) => build(k, kind, nps, rating, L, temperature, tag), [kind, nps, rating, L, temperature, tag])\n  const cone = useKit((k) => {\n    if (!spray) return\n    const a = (Math.max(10, Math.min(175, angle)) * Math.PI) / 360\n    const len = kind === 'hv' ? 1.2 : 0.8\n    const r = Math.min(3, Math.tan(a) * len)\n    k.cyl(WATER, 0.006, kind === 'sprinkler' ? 0.25 : len, [0, -0.04 - (kind === 'sprinkler' ? 0.125 : len / 2), 0], '-y', 32, kind === 'sprinkler' ? 0.6 : r, true)\n  }, [spray, angle, kind])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n      <KitMesh data={cone} shadows={false} />\n    </group>\n  )\n}\n\nexport default DelugeNozzle\n\nfunction bulbColour(t: number) {\n  let c = BULB[0][1]\n  for (const [deg, col] of BULB) if (t >= deg) c = col\n  return c\n}\n\n/** Male ½\" NPT thread and wrench hex of a brass head, thread top at y = 0.03. */\nfunction threadHex(k: Kit, mat: MatKey) {\n  k.cyl(mat, 0.0105, 0.014, [0, 0.023, 0], 'y', 20)\n  for (let i = 0; i < 4; i++) k.torus(mat, 0.0106, 0.0008, [0, 0.018 + i * 0.0032, 0], 'y', 4, 20)\n  k.hex(mat, 0.03, 0.012, [0, 0.01, 0], 'y')\n}\n\nfunction build(k: Kit, kind: NozzleKind, nps: number, rating: Rating, L: number, temperature: number, tag: string) {\n  const D = pipeOD(nps)\n  // flanged drop: weld-neck flange (port A), pipe, forged half-coupling reducing to ½\" NPT\n  const fl = flange(k, nps, rating, [0, L, 0], 'y', true)\n  portBore(k, nps, rating, [0, L, 0], 'y')\n  const yc = 0.045\n  k.pipe('galvanised', [0, yc + 0.03, 0], [0, L - fl, 0], D, pipeWall(nps, '80'))\n  k.torus('flangeSteel', D / 2 + 0.0008, 0.0025, [0, L - fl, 0], 'y', 6, 24)\n  k.cyl('galvanised', 0.016, yc + 0.03 - 0.025, [0, (yc + 0.03 + 0.025) / 2, 0], 'y', 24, D / 2 + 0.004)\n  tagPlate(k, tag, [0, L - fl - 0.04, D / 2 + 0.006], 'z', 0.06)\n  k.rod('stainless', [-0.03, L - fl - 0.04 + 0.009, D / 2 + 0.004], [-0.03, L - fl - 0.04 + 0.009, 0], 0.0012, 6)\n  const brass: MatKey = 'brass'\n  if (kind === 'mv') {\n    // body with orifice (hollow), two frame arms to an external slotted deflector\n    threadHex(k, brass)\n    k.lathe(brass, [[0.007, 0.004], [0.007, -0.008], [0.013, -0.008], [0.015, 0.004]], [0, 0, 0], 'y', 24)\n    k.pipe('pipeBore', [0, 0.004, 0], [0, -0.008, 0], 0.012, 0.0004, 16)\n    for (const s of [-1, 1]) k.box(brass, [0.004, 0.034, 0.006], [s * 0.014, -0.025, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -s * 0.18))\n    k.cyl(brass, 0.006, 0.01, [0, -0.04, 0], 'y', 16)\n    k.lathe(brass, [[0.001, -0.045], [0.004, -0.045], [0.017, -0.039], [0.018, -0.042], [0.001, -0.049]], [0, 0, 0], 'y', 32)\n    for (let i = 0; i < 12; i++) {\n      const a = (i / 12) * Math.PI * 2\n      k.box('pipeBore', [0.006, 0.0015, 0.0018], [Math.cos(a) * 0.014, -0.0402, Math.sin(a) * 0.014], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), -a).multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -0.45)))\n    }\n  } else if (kind === 'hv') {\n    // conical body with swirl plate and a hollow open orifice facing down\n    threadHex(k, brass)\n    k.lathe(brass, [[0.006, -0.05], [0.009, -0.05], [0.016, -0.02], [0.016, 0.004], [0.012, 0.004], [0.012, -0.02], [0.006, -0.046]], [0, 0, 0], 'y', 32)\n    k.disc('pipeBore', 0.0105, [0, -0.012, 0], '-y', 24)\n    k.box(brass, [0.022, 0.003, 0.003], [0, -0.0118, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), 0.6))\n    k.torus(brass, 0.0075, 0.0015, [0, -0.05, 0], 'y', 6, 20)\n  } else if (kind === 'sprinkler') {\n    // pendent sprinkler: yoke arms, glass bulb on the orifice cap, serrated deflector\n    threadHex(k, brass)\n    k.cyl(brass, 0.011, 0.008, [0, 0, 0], 'y', 20)\n    k.cyl('stainless', 0.006, 0.004, [0, -0.006, 0], 'y', 16)\n    const bulb = paint(bulbColour(temperature), { roughness: 0.08, metalness: 0, clearcoat: 1, clearcoatRoughness: 0.05, weather: 0 })\n    k.sphere(bulb, 0.0045, [0, -0.02, 0], [1, 2.6, 1], 16)\n    for (const s of [-1, 1]) {\n      k.tube(brass, [[s * 0.011, 0.002, 0], [s * 0.014, -0.02, 0], [s * 0.004, -0.036, 0]], 0.0024, 6, 0.006)\n    }\n    k.cyl(brass, 0.005, 0.006, [0, -0.036, 0], 'y', 16)\n    k.cyl(brass, 0.02, 0.0015, [0, -0.04, 0], 'y', 36)\n    for (let i = 0; i < 18; i++) {\n      const a = (i / 18) * Math.PI * 2\n      k.box('pipeBore', [0.006, 0.0018, 0.0016], [Math.cos(a) * 0.017, -0.04, Math.sin(a) * 0.017], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), -a))\n    }\n  } else {\n    // water-mist head: stainless body with nine micro-nozzles, each with a hollow orifice\n    k.cyl('stainless', 0.0105, 0.014, [0, 0.023, 0], 'y', 20)\n    k.hex('stainless', 0.032, 0.014, [0, 0.009, 0], 'y')\n    k.dome('stainless', 0.02, [0, 0.002, 0], '-y', Math.PI / 2, 1, 32)\n    const dirs: THREE.Vector3[] = [new THREE.Vector3(0, -1, 0)]\n    for (let i = 0; i < 8; i++) {\n      const a = (i / 8) * Math.PI * 2\n      dirs.push(new THREE.Vector3(Math.cos(a) * 0.7, -0.71, Math.sin(a) * 0.7).normalize())\n    }\n    for (const d of dirs) {\n      const p0 = d.clone().multiplyScalar(0.018).add(new THREE.Vector3(0, 0.002, 0))\n      const p1 = d.clone().multiplyScalar(0.026).add(new THREE.Vector3(0, 0.002, 0))\n      k.rod('stainless', p0.toArray() as [number, number, number], p1.toArray() as [number, number, number], 0.0035, 12)\n      k.disc('pipeBore', 0.0012, p1.clone().addScaledVector(d, 0.0002).toArray() as [number, number, number], d.toArray() as [number, number, number], 10)\n    }\n  }\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": "DelugeNozzle",
    "anchor": "fitting",
    "standards": [
      "NFPA 15",
      "NFPA 13",
      "EN 12259-1",
      "ISO 6182-1",
      "ASME B16.5"
    ]
  }
}