{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wellhead-control-panel",
  "type": "registry:component",
  "title": "Wellhead control panel (WHCP)",
  "description": "Skid-mounted hydraulic wellhead control panel. A stainless cabinet with louvres and a canopy holds one control module per well (SCSSV and SSV gauges, pilot relay block, selector valves, trip / reset buttons), supply / HP / LP header gauges, the hydraulic reservoir with sight glass, two air-driven pumps, an electric pump and filters, all tubed to a bulkhead plate. The side carries an ESD push-button, fusible-loop and hi/lo pilot connections and an air filter-regulator. An open rack beside it carries nitrogen-precharged bladder accumulators. The double doors swing open with `doorsOpen`. No ports: the outputs are 1/4\"–1/2\" control tubing.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "wellhead/wellhead-control-panel.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/wellhead/wellhead-control-panel.tsx",
      "content": "// Wellhead control panel (WHCP): stainless cabinet with per-well SSV / SCSSV modules, gauges, reservoir, air and electric pumps, accumulator rack, on a skid. API 6A / API 14D / ISO 10418.\nimport * as THREE from 'three'\nimport { uvScale, type Kit } from '../lib/kit'\nimport { KitMesh, useKit, type GroupProps } from '../lib/mesh'\nimport { gauge, junctionBox, liftingLug, member, skid, tagPlate } from '../lib/parts'\nimport { louvreMaterial, textMaterial } from '../lib/textures'\n\nexport interface WellheadControlPanelProps {\n  /** number of well control modules */\n  wells?: number\n  /** accumulator bottles on the rack */\n  accumulators?: number\n  doorsOpen?: boolean\n  tag?: string\n}\n\nexport const defaults = { wells: 6, accumulators: 6, doorsOpen: true, tag: 'X-N270' }\n\ntype P = typeof defaults\n\nconst SKID = 'HEB 160'\nconst H = 2.0 // cabinet height, typical\nconst D = 0.75 // cabinet depth\nconst MOD = 0.26 // module width per well\n\nfunction layout(p: P) {\n  const n = Math.max(1, Math.round(p.wells))\n  const Lc = Math.max(1.4, n * MOD + 0.3)\n  const nb = Math.max(2, Math.round(p.accumulators))\n  const La = Math.ceil(nb / 2) * 0.32 + 0.2\n  const L = Lc + La + 0.3\n  const W = D + 0.6\n  const yb = 0.16 + 0.008\n  const xc = -L / 2 + 0.1 + Lc / 2\n  const xa = xc + Lc / 2 + 0.15 + La / 2\n  return { n, Lc, nb, La, L, W, yb, xc, xa }\n}\n\nexport function WellheadControlPanel(props: WellheadControlPanelProps & GroupProps) {\n  const { wells, accumulators, doorsOpen, tag, ...group } = { ...defaults, ...props }\n  const p = { wells, accumulators, doorsOpen, tag } as P\n  const L = layout(p)\n  const data = useKit((k) => build(k, p), Object.values(p))\n  const door = useKit((k) => buildDoor(k, L.Lc / 2, true), [L.Lc])\n  const door2 = useKit((k) => buildDoor(k, L.Lc / 2, false), [L.Lc])\n  const z = -0.15 + D / 2\n  const y = L.yb + 0.08\n  const a = doorsOpen ? (110 * Math.PI) / 180 : 0\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n      <group position={[L.xc - L.Lc / 2, y, z]} rotation-y={-a}>\n        <KitMesh data={door} />\n      </group>\n      <group position={[L.xc + L.Lc / 2, y, z]} rotation-y={a} scale-x={-1}>\n        <KitMesh data={door2} />\n      </group>\n    </group>\n  )\n}\n\nexport default WellheadControlPanel\n\n/** One door leaf, hinge at the origin, leaf along +X, outside face +Z. */\nfunction buildDoor(k: Kit, w: number, label: boolean) {\n  const t = 0.025\n  k.rbox('stainless', [w - 0.01, H - 0.02, t], 0.006, [w / 2, H / 2, t / 2])\n  k.box('stainless', [w - 0.1, H - 0.12, 0.004], [w / 2, H / 2, t + 0.002])\n  // hinges, quarter-turn handle with padlock hasp, document pocket inside\n  for (const y of [0.25, H / 2, H - 0.25]) k.cyl('stainless', 0.012, 0.09, [0, y, t / 2], 'y', 10)\n  k.rbox('plasticGrey', [0.04, 0.16, 0.03], 0.008, [w - 0.06, H * 0.5, t + 0.015])\n  k.box('handwheel', [0.025, 0.1, 0.02], [w - 0.06, H * 0.5, t + 0.04])\n  k.box('stainless', [w * 0.5, 0.3, 0.03], [w * 0.5, H * 0.75, -0.015])\n  if (label) k.box(textMaterial('WHCP', '#c3c7cb', '#1a1a1a', 256, 96), [0.2, 0.075, 0.002], [w * 0.5, H * 0.85, t + 0.005])\n}\n\nfunction bottle(k: Kit, x: number, z: number, y0: number, h: number, r: number) {\n  k.cyl('actuatorRed', r, h - 2 * r, [x, y0 + h / 2, z], 'y', 24)\n  k.dome('actuatorRed', r, [x, y0 + h - r, z], 'y', Math.PI / 2, 1, 20)\n  k.dome('actuatorRed', r, [x, y0 + r, z], '-y', Math.PI / 2, 1, 20)\n  k.cyl('brass', r * 0.25, 0.05, [x, y0 + h + 0.02, z], 'y', 10)\n  k.cyl('stainless', r * 0.3, 0.06, [x, y0 - 0.02, z], 'y', 10)\n  k.box(textMaterial('N2', '#1a1a1a', '#ffffff', 128, 64), [r * 0.8, r * 0.4, 0.002], [x, y0 + h * 0.7, z + r + 0.001])\n}\n\nfunction build(k: Kit, p: P) {\n  const L = layout(p)\n  const top = skid(k, L.L, L.W, SKID, 'galvanised')\n  const y0 = top + 0.08 // cabinet on plinth feet\n  const zb = -0.15 - D / 2 // cabinet back\n  const zf = -0.15 + D / 2 // cabinet front\n  const x0 = L.xc - L.Lc / 2\n  const x1 = L.xc + L.Lc / 2\n\n  // cabinet shell: plinth, back, sides, roof canopy, louvred sides\n  for (const x of [x0 + 0.08, x1 - 0.08]) for (const z of [zb + 0.08, zf - 0.08]) k.box('stainless', [0.08, 0.08, 0.08], [x, top + 0.04, z])\n  const t = 0.02\n  k.box('stainless', [L.Lc, t, D], [L.xc, y0 + t / 2, -0.15])\n  k.box('stainless', [L.Lc, H, t], [L.xc, y0 + H / 2, zb + t / 2])\n  for (const x of [x0 + t / 2, x1 - t / 2]) k.box('stainless', [t, H, D], [x, y0 + H / 2, -0.15])\n  const lv = new THREE.BoxGeometry(0.004, 0.5, D * 0.6)\n  uvScale(lv, 1, 6)\n  for (const x of [x0 - 0.002, x1 + 0.002]) k.add(louvreMaterial('#c3c7cb'), lv.clone(), new THREE.Matrix4().setPosition(x, y0 + H * 0.25, -0.15))\n  k.rbox('stainless', [L.Lc + 0.12, 0.04, D + 0.16], 0.01, [L.xc, y0 + H + 0.02, -0.12])\n  for (const x of [x0 + 0.1, x1 - 0.1]) liftingLug(k, [x, y0 + H + 0.04, -0.15], 'x', 0.7, 'stainless')\n  tagPlate(k, p.tag, [x0 - 0.002 - 0.001, y0 + H * 0.75, -0.15], '-x', 0.25)\n\n  // mounting plate and per-well control modules (SSV and SCSSV)\n  const zp = zb + 0.06\n  k.box('equipmentPaint', [L.Lc - 0.08, H - 0.15, 0.01], [L.xc, y0 + H / 2 + 0.05, zp])\n  const yHead = y0 + H - 0.22\n  // common header gauges: supply air, HP and LP hydraulic headers\n  const xs = L.xc - (L.n * MOD) / 2\n  for (let i = 0; i < 3; i++) gauge(k, [L.xc + (i - 1) * 0.22, yHead + 0.08, zp + 0.05], 'z', 0.12, 'bar', [16, 700, 250][i])\n  k.box(textMaterial('SUPPLY   HP   LP', '#1f3e70', '#ffffff', 512, 64), [0.66, 0.05, 0.002], [L.xc, yHead - 0.02, zp + 0.006])\n  for (let i = 0; i < L.n; i++) {\n    const x = xs + MOD * (i + 0.5)\n    k.box(textMaterial(`WELL ${i + 1}`, '#1f3e70', '#ffffff', 256, 64), [MOD * 0.85, 0.05, 0.002], [x, yHead - 0.12, zp + 0.006])\n    // SCSSV (top) and SSV gauges\n    gauge(k, [x, yHead - 0.25, zp + 0.05], 'z', 0.1, 'bar', 700)\n    gauge(k, [x, yHead - 0.4, zp + 0.05], 'z', 0.1, 'bar', 250)\n    // pilot relay / logic block with selector valves\n    k.rbox('aluminium', [MOD * 0.7, 0.24, 0.07], 0.008, [x, yHead - 0.62, zp + 0.04])\n    for (const [dy, col] of [[-0.05, 'actuatorRed'], [0.05, 'safetyGreen']] as const) {\n      k.cyl('stainless', 0.012, 0.04, [x, yHead - 0.62 + dy, zp + 0.09], 'z', 10)\n      k.box(col, [0.07, 0.015, 0.012], [x + 0.02, yHead - 0.62 + dy, zp + 0.115])\n    }\n    // reset / trip push-pull buttons\n    k.cyl('actuatorRed', 0.016, 0.03, [x - MOD * 0.25, yHead - 0.8, zp + 0.025], 'z', 12)\n    k.cyl('safetyGreen', 0.016, 0.03, [x + MOD * 0.25, yHead - 0.8, zp + 0.025], 'z', 12)\n    // tubing from the module down to the bulkhead\n    k.tube('tubing', [[x - 0.03, yHead - 0.74, zp + 0.04], [x - 0.03, y0 + 0.75, zp + 0.04], [x - 0.03, y0 + 0.6, zp + 0.1 + (i % 2) * 0.02], [x1 - 0.06, y0 + 0.6, zp + 0.1 + (i % 2) * 0.02]], 0.005, 6, 0.04)\n    k.tube('tubing', [[x + 0.03, yHead - 0.74, zp + 0.04], [x + 0.03, y0 + 0.7, zp + 0.04], [x + 0.03, y0 + 0.55, zp + 0.14 + (i % 2) * 0.02], [x1 - 0.06, y0 + 0.55, zp + 0.14 + (i % 2) * 0.02]], 0.005, 6, 0.04)\n  }\n  // reservoir with sight glass and filler/breather, two air-driven pumps, one electric pump, filters\n  const rx = L.xc - L.Lc * 0.2\n  const rw = L.Lc * 0.5\n  k.rbox('stainless', [rw, 0.42, D - 0.15], 0.015, [rx, y0 + 0.23, -0.15 - 0.03])\n  k.box('glass', [0.03, 0.25, 0.01], [rx + rw * 0.35, y0 + 0.24, -0.15 + (D - 0.15) / 2 - 0.02])\n  k.cyl('liquidOil', 0.012, 0.14, [rx + rw * 0.35, y0 + 0.19, -0.15 + (D - 0.15) / 2 - 0.025], 'y', 8)\n  k.cyl('stainless', 0.04, 0.06, [rx - rw * 0.3, y0 + 0.47, -0.2], 'y', 16)\n  k.cyl('handwheel', 0.045, 0.02, [rx - rw * 0.3, y0 + 0.51, -0.2], 'y', 16)\n  const px = L.xc + L.Lc * 0.28\n  for (let j = 0; j < 2; j++) {\n    const zz = -0.32 + j * 0.24\n    k.cyl('aluminium', 0.065, 0.24, [px, y0 + 0.16, zz], 'y', 20)\n    k.dome('actuatorBlue', 0.07, [px, y0 + 0.28, zz], 'y', Math.PI / 2, 0.8, 18)\n    k.cyl('stainless', 0.03, 0.14, [px, y0 + 0.4, zz], 'y', 12)\n    k.cyl('stainless', 0.022, 0.08, [px + 0.08, y0 + 0.1, zz], 'x', 10)\n  }\n  k.cyl('motorPaint', 0.08, 0.26, [px - 0.05, y0 + 0.62, -0.2], 'x', 20)\n  k.cyl('stainless', 0.05, 0.12, [px + 0.14, y0 + 0.62, -0.2], 'x', 16)\n  for (let j = 0; j < 2; j++) k.cyl('stainless', 0.035, 0.18, [px - 0.22 + j * 0.09, y0 + 0.62, -0.05], 'y', 14)\n  // bulkhead plate on the right side with outgoing tubing to the wells\n  k.box('stainless', [0.01, 0.4, 0.5], [x1 + 0.005, y0 + 0.55, -0.15])\n  for (let i = 0; i < L.n * 2; i++) {\n    const yy = y0 + 0.4 + (i % 4) * 0.08\n    const zz = -0.35 + Math.floor(i / 4) * 0.07\n    k.hex('brass', 0.022, 0.03, [x1 + 0.025, yy, zz], 'x')\n  }\n  junctionBox(k, [x0 - 0.002, y0 + 1.1, -0.15], '-x', [0.3, 0.35, 0.16])\n  k.spline('cable', [[x0 - 0.08, y0 + 0.9, -0.15], [x0 - 0.15, y0 + 0.4, -0.15], [x0 - 0.2, top + 0.05, -0.1]], 0.012, 6, 16)\n\n  // accumulator rack: frame, bottles in two rows, clamp bands, manifold\n  const ax0 = L.xa - L.La / 2\n  const ax1 = L.xa + L.La / 2\n  const ah = 1.5\n  for (const x of [ax0, ax1]) for (const z of [-0.45, 0.15]) member(k, [x, top, z], [x, top + ah + 0.1, z], 'L50x50x5', 'galvanised')\n  for (const y of [top + 0.05, top + ah * 0.55, top + ah + 0.1])\n    for (const z of [-0.45, 0.15]) member(k, [ax0, y, z], [ax1, y, z], 'L50x50x5', 'galvanised')\n  const r = 0.11\n  for (let i = 0; i < L.nb; i++) {\n    const col = Math.floor(i / 2)\n    const x = ax0 + 0.2 + col * 0.32\n    const z = i % 2 ? 0.0 : -0.3\n    bottle(k, x, z, top + 0.08, ah - 0.15, r)\n    k.torus('galvanised', r + 0.004, 0.008, [x, top + ah * 0.55, z], 'y', 6, 24)\n  }\n  k.pipe('stainless', [ax0 + 0.05, top + 0.06, -0.15], [ax1 - 0.05, top + 0.06, -0.15], 0.034, 0.005, 12)\n  k.tube('tubing', [[ax0 + 0.05, top + 0.06, -0.15], [x1 + 0.08, top + 0.06, -0.15], [x1 + 0.08, y0 + 0.45, -0.15], [x1 + 0.02, y0 + 0.45, -0.15]], 0.008, 6, 0.05)\n  gauge(k, [ax1 + 0.03, top + 0.9, -0.15], 'x', 0.1, 'bar', 700)\n  k.box('equipmentPaint', [0.1, 0.06, 0.06], [ax1 - 0.02, top + 0.9, -0.15])\n  // bundled control lines leaving the skid towards the wells\n  // outgoing control lines to a skid-edge bulkhead plate (field tubing connects there)\n  k.box('stainless', [0.3, 0.12, 0.01], [x1 + 0.23, top + 0.06, -L.W / 2 + 0.005])\n  for (let i = 0; i < 4; i++) {\n    k.tube('tubing', [[x1 + 0.03, y0 + 0.4 + i * 0.08, -0.35], [x1 + 0.2 + i * 0.02, y0 + 0.4 + i * 0.08, -0.35], [x1 + 0.2 + i * 0.02, top + 0.06, -0.35], [x1 + 0.2 + i * 0.02, top + 0.06, -L.W / 2 + 0.01]], 0.006, 6, 0.04)\n    k.hex('brass', 0.022, 0.03, [x1 + 0.2 + i * 0.02, top + 0.06, -L.W / 2 - 0.01], 'z')\n  }\n  // ESD push-button station and fusible-loop / hi-lo pilot connections on the left side\n  {\n    const xe = x0 - 0.002\n    k.rbox('actuatorYellow', [0.06, 0.26, 0.2], 0.01, [xe - 0.03, y0 + 1.5, 0.16])\n    k.cyl('actuatorRed', 0.03, 0.03, [xe - 0.075, y0 + 1.5, 0.16], 'x', 16)\n    k.dome('actuatorRed', 0.045, [xe - 0.09, y0 + 1.5, 0.16], '-x', Math.PI / 2, 0.6, 16)\n    k.box(textMaterial('ESD', '#e1b10f', '#111111', 128, 64), [0.002, 0.04, 0.08], [xe - 0.061, y0 + 1.6, 0.16])\n    for (const [i, label] of ['FUSIBLE LOOP', 'HI/LO PILOT'].entries()) {\n      const yy = y0 + 0.5 + i * 0.14\n      k.box(textMaterial(label, '#c3c7cb', '#111111', 256, 48), [0.002, 0.03, 0.16], [xe - 0.001, yy + 0.05, 0.12])\n      k.hex('brass', 0.03, 0.03, [xe - 0.015, yy, 0.12], 'x')\n      k.tube('tubing', [[xe - 0.03, yy, 0.12], [xe - 0.12 - i * 0.04, yy, 0.12], [xe - 0.12 - i * 0.04, top + 0.06, 0.12], [xe - 0.12 - i * 0.04, top + 0.06, L.W / 2 - 0.01]], 0.005, 6, 0.04)\n      k.hex('brass', 0.022, 0.03, [xe - 0.12 - i * 0.04, top + 0.06, L.W / 2 + 0.01], 'z')\n    }\n    // instrument air filter-regulator with gauge\n    k.cyl('aluminium', 0.04, 0.16, [xe - 0.06, y0 + 0.95, 0.12], 'y', 16)\n    k.cyl('glass', 0.035, 0.08, [xe - 0.06, y0 + 0.84, 0.12], 'y', 16)\n    gauge(k, [xe - 0.06, y0 + 1.07, 0.18], 'z', 0.06, 'bar', 16)\n  }\n  k.box('stainless', [0.2, 0.12, 0.01], [x0 - 0.14, top + 0.06, L.W / 2 - 0.005])\n  // earth boss\n  k.cyl('brass', 0.015, 0.03, [x0 + 0.2, top - 0.05, L.W / 2 + 0.01], 'z', 10)\n}\n"
    }
  ],
  "categories": [
    "wellhead"
  ],
  "meta": {
    "export": "WellheadControlPanel",
    "anchor": "base",
    "standards": [
      "API 6A",
      "API 14D",
      "API RP 14C",
      "ISO 10418",
      "IEC 60079"
    ]
  }
}