{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "centrifugal-pump",
  "type": "registry:component",
  "title": "Centrifugal pump (API 610 OH2)",
  "description": "Overhung, centreline-mounted end-suction pump with volute casing, bearing housing, spacer coupling under a mesh guard and a TEFC motor on a drain-rim baseplate. Includes an API Plan 53A seal reservoir and a local control station. Motor frame from IEC 60072 for the rated power.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "rotating/centrifugal-pump.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/rotating/centrifugal-pump.tsx",
      "content": "// API 610 OH2 centrifugal pump package: end suction, top discharge, flexible coupling, TEFC motor, drain-rim baseplate, Plan 53A seal pot.\nimport * as THREE from 'three'\nimport { useEffect, useMemo } from 'react'\nimport { Kit, sweepGeometry, type MatKey, type V3 } from '../lib/kit'\nimport { KitMesh, type GroupProps } from '../lib/mesh'\nimport { flange, gauge, motor, tagPlate } from '../lib/parts'\nimport { port, type Port } from '../lib/ports'\nimport { Rotors, cowlFan, guardedCoupling, type RotorSpec } from '../lib/rotors'\nimport { pipeOD, pipeWall, type Rating } from '../lib/standards'\n\nexport interface CentrifugalPumpProps {\n  suctionNps?: number\n  dischargeNps?: number\n  rating?: Rating\n  /** motor rated power, kW */\n  kw?: number\n  stages?: number\n  baseLength?: number\n  baseWidth?: number\n  casing?: MatKey\n  tag?: string\n  /** spin the coupling and motor fan */\n  running?: boolean\n}\n\nexport const defaults = {\n  suctionNps: 6,\n  dischargeNps: 4,\n  rating: 300 as Rating,\n  kw: 110,\n  stages: 1,\n  baseLength: 2.8,\n  baseWidth: 1.1,\n  casing: 'machineryGreen' as MatKey,\n  running: true,\n}\n\nconst NECK = 0.25\n\nfunction nozzleBases(p: Omit<typeof defaults, 'running'>) {\n  const sy = 0.55 + 0.03 * p.suctionNps\n  return {\n    suction: { base: [p.baseLength * 0.31, sy, 0] as V3, nps: p.suctionNps },\n    discharge: { base: [p.baseLength * 0.19, sy + 0.35, 0] as V3, nps: p.dischargeNps },\n  }\n}\n\nexport function ports(props: CentrifugalPumpProps = {}): Port[] {\n  const p = { ...defaults, ...props }\n  const { suction, discharge } = nozzleBases(p)\n  return [\n    port('suction', 'Suction', [suction.base[0] + NECK, suction.base[1], 0], [1, 0, 0], p.suctionNps, p.rating),\n    port('discharge', 'Discharge', [discharge.base[0], discharge.base[1] + NECK, 0], [0, 1, 0], p.dischargeNps, p.rating),\n  ]\n}\n\nexport function CentrifugalPump({ tag, ...rest }: CentrifugalPumpProps & GroupProps) {\n  const { suctionNps, dischargeNps, rating, kw, stages, baseLength, baseWidth, casing, running, ...group } = { ...defaults, ...rest }\n  const p = { suctionNps, dischargeNps, rating, kw, stages, baseLength, baseWidth, casing }\n  const { built, rotors, necks } = useMemo(() => {\n    const out = build({ ...p, tag })\n    const nk = new Kit()\n    const { suction, discharge } = nozzleBases(p)\n    for (const [n, dir] of [[suction, [1, 0, 0]], [discharge, [0, 1, 0]]] as const) {\n      const face: V3 = [n.base[0] + dir[0] * NECK, n.base[1] + dir[1] * NECK, 0]\n      const L = flange(nk, n.nps, rating, face, dir as unknown as V3, true)\n      nk.pipe(casing, n.base, [face[0] - dir[0] * L, face[1] - dir[1] * L, 0], pipeOD(n.nps), pipeWall(n.nps, '80'))\n    }\n    return { ...out, necks: nk.build() }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [suctionNps, dischargeNps, rating, kw, stages, baseLength, baseWidth, casing, tag])\n  useEffect(() => () => [built, necks, ...rotors.map((r) => r.built)].forEach((b) => b.geometry.dispose()), [built, necks, rotors])\n  return (\n    <group {...group}>\n      <KitMesh data={built} />\n      <KitMesh data={necks} />\n      <Rotors rotors={rotors} running={running} />\n    </group>\n  )\n}\n\nexport default CentrifugalPump\n\nconst BASE: MatKey = 'equipmentPaintDark'\n\nconst THE = Math.PI / 4\n\n// Volute scroll in the YZ plane, centred on the shaft.\nclass ScrollCurve extends THREE.Curve<THREE.Vector3> {\n  xc: number\n  ys: number\n  zc: number\n  Rimp: number\n  r0: number\n  r1: number\n  th0: number\n  constructor(xc: number, ys: number, zc: number, Rimp: number, r0: number, r1: number, th0: number) {\n    super()\n    this.xc = xc\n    this.ys = ys\n    this.zc = zc\n    this.Rimp = Rimp\n    this.r0 = r0\n    this.r1 = r1\n    this.th0 = th0\n  }\n  r(t: number) {\n    return Math.sqrt(this.r0 * this.r0 + (this.r1 * this.r1 - this.r0 * this.r0) * t)\n  }\n  getPoint(t: number, target = new THREE.Vector3()) {\n    const th = THE - 2 * Math.PI + this.th0 + t * (2 * Math.PI - this.th0)\n    const R = this.Rimp + this.r(t)\n    return target.set(this.xc, this.ys + Math.sin(th) * R, this.zc + Math.cos(th) * R)\n  }\n}\n\nfunction build(p: Omit<typeof defaults, 'running'> & { tag?: string }) {\n  const k = new Kit()\n  const BL = p.baseLength\n  const BW = p.baseWidth\n  const { suction, discharge } = nozzleBases(p)\n  const C = p.casing\n  const ys = suction.base[1]\n  const xs = suction.base[0]\n  const xc = discharge.base[0]\n  const zc = 0\n  const H = discharge.base[1] - ys\n  const dNps = discharge.nps\n  const sNps = suction.nps\n  const hb = Math.min(0.3, ys - 0.3) // baseplate top height\n\n  // volute casing\n  const r1 = Math.max((pipeOD(dNps) / 2) * 1.08, 0.05)\n  const r0 = r1 * 0.32\n  const Rimp = Math.min(Math.max(0.5 * H, 0.1), 0.5)\n  const Rend = Rimp + r1\n  const scroll = new ScrollCurve(xc, ys, zc, Rimp, r0, r1, 0.5)\n  k.add(C, sweepGeometry(scroll, (t) => [scroll.r(t), scroll.r(t) * 0.88], 72, 24, true))\n  // throat: S-bend from the scroll end up into the discharge nozzle base\n  const P0 = new THREE.Vector3(xc, ys + Math.sin(THE) * Rend, zc + Math.cos(THE) * Rend)\n  const P3 = new THREE.Vector3(xc, ys + H + 0.02, zc)\n  const rise = P3.y - P0.y\n  const T0 = new THREE.Vector3(0, Math.cos(THE), -Math.sin(THE))\n  const throat = new THREE.CubicBezierCurve3(P0, P0.clone().addScaledVector(T0, rise * 0.55), P3.clone().add(new THREE.Vector3(0, -rise * 0.5, 0)), P3)\n  const rTop = (pipeOD(dNps) / 2) * 1.06\n  k.add(C, sweepGeometry(throat, (t) => {\n    const r = r1 + (rTop - r1) * t\n    return [r, r * (0.88 + 0.12 * t)]\n  }, 24, 24, false))\n  // hub (impeller chamber) + front / back covers\n  const wHub = r1 * 0.85\n  const Rhub = Rimp + r0 * 1.2\n  k.cyl(C, Rhub, wHub * 2, [xc, ys, zc], 'x', 40)\n  // web joining throat to hub so there is no visual gap\n  k.box(C, [wHub * 1.4, Rhub * 0.5, Rhub * 0.9], [xc, ys + Rhub * 0.75, zc + Rhub * 0.25])\n  // discharge boss\n  k.cyl(C, rTop * 1.12, 0.04, [xc, ys + H - 0.0, zc], 'y', 28)\n  // cover joint flanges with studs (front and back)\n  const Rj = Rhub + 0.035\n  for (const s of [-1, 1]) {\n    const xj = xc + s * wHub\n    k.cyl(C, Rj, 0.03, [xj + s * 0.015, ys, zc], 'x', 40)\n    k.boltCircle('boltXylan', 'nut', 12, Rj - 0.018, [xj - (s > 0 ? -0.0 : 0.05), ys, zc], 'x', 0.016, 0.05, s > 0 ? [0.045] : [0.005])\n  }\n  // casing vent (top) + drain (bottom) plugs\n  k.hex('nut', 0.03, 0.02, [xc - wHub * 0.5, ys + Rhub + 0.005, zc - Rhub * 0.3])\n  k.cyl(C, 0.025, 0.06, [xc, ys - Rend - r1 * 0.6, zc], 'y', 14)\n  k.hex('nut', 0.034, 0.02, [xc, ys - Rend - r1 * 0.6 - 0.035, zc], 'y')\n\n  // stage rings + suction bell\n  const front = xc + wHub + 0.03\n  let x = front\n  const extra = Math.max(0, p.stages - 1)\n  const sR = (pipeOD(sNps) / 2) * 1.08\n  const avail = xs - front\n  const ringW = extra ? Math.min(0.14, (avail * 0.6) / extra) : 0\n  for (let i = 0; i < extra; i++) {\n    k.lathe(C, [\n      [Rhub * 0.85, x],\n      [Rhub * 0.98, x + ringW * 0.15],\n      [Rhub * 0.98, x + ringW * 0.85],\n      [Rhub * 0.85, x + ringW],\n      [Rhub * 0.4, x + ringW],\n    ], [0, ys, zc], 'x', 36)\n    x += ringW\n  }\n  if (extra) {\n    // tie bolts across the stage rings\n    k.around(6, Rhub * 0.98, [0, ys, zc], 'x', (p) => {\n      k.rod('boltXylan', [front - 0.01, p[1], p[2]], [x + 0.03, p[1], p[2]], 0.009, 6)\n      k.hex('nut', 0.03, 0.02, [x + 0.02, p[1], p[2]], 'x')\n    }, Math.PI / 6)\n  }\n  // suction bell (lathe along +X) up to the suction nozzle base\n  const bellEnd = Math.max(xs + 0.05, x + 0.06)\n  k.lathe(C, [\n    [Rhub * 0.8, x - 0.02],\n    [Rhub * 0.72, x + 0.02],\n    [sR * 1.15, x + (bellEnd - x) * 0.6],\n    [sR, bellEnd],\n    [0, bellEnd],\n  ], [0, ys, zc], 'x', 36)\n\n  // seal chamber + bearing frame\n  const back = xc - wHub - 0.03\n  const sealL = 0.14\n  const sealR = Math.max(0.07, Rhub * 0.42)\n  k.cyl(C, sealR, sealL, [back - sealL / 2, ys, zc], 'x', 32)\n  // seal gland plate with 4 studs + plan 53 ports\n  const xg = back - sealL - 0.015\n  k.cyl('stainless', sealR * 1.15, 0.03, [xg, ys, zc], 'x', 28)\n  k.boltCircle('boltXylan', 'nut', 4, sealR * 0.95, [xg - 0.035, ys, zc], 'x', 0.012, 0.06, [0.006], Math.PI / 4)\n  const portIn: V3 = [xg, ys + sealR * 1.15 + 0.02, zc - 0.03]\n  const portOut: V3 = [xg, ys + sealR * 1.15 + 0.02, zc + 0.03]\n  for (const p of [portIn, portOut]) k.cyl('stainless', 0.011, 0.04, [p[0], p[1] - 0.02, p[2]], 'y', 8)\n  // bearing bracket (lantern with windows) then bearing housing\n  const brL = 0.18\n  const xbr = xg - 0.015\n  k.lathe(C, [\n    [sealR * 1.1, xbr - brL],\n    [Rhub * 0.55, xbr - brL * 0.1],\n    [Rhub * 0.62, xbr],\n  ], [0, ys, zc], 'x', 32)\n  const bhL = Math.max(0.36, Rhub * 1.4)\n  const bhR = Math.max(0.1, Rhub * 0.48)\n  const xbh = xbr - brL\n  k.lathe(C, [\n    [bhR * 0.75, xbh - bhL],\n    [bhR, xbh - bhL + 0.03],\n    [bhR, xbh - 0.03],\n    [bhR * 0.9, xbh],\n  ], [0, ys, zc], 'x', 36)\n  // cooling fins on bearing housing\n  for (let i = 0; i < 6; i++) k.cyl(C, bhR * 1.12, 0.008, [xbh - bhL * 0.2 - i * bhL * 0.11, ys, zc], 'x', 32)\n  // bearing end cover + shaft\n  k.cyl(C, bhR * 0.7, 0.03, [xbh - bhL - 0.015, ys, zc], 'x', 28)\n  // oil sight glass (bullseye), constant-level oiler, breather\n  k.cyl('brass', bhR * 0.22, 0.012, [xbh - bhL * 0.5, ys - bhR * 0.35, zc + bhR * 0.96], 'z', 18)\n  k.disc('glass', bhR * 0.17, [xbh - bhL * 0.5, ys - bhR * 0.35, zc + bhR * 0.96 + 0.0065], 'z', 18)\n  k.rod('stainless', [xbh - bhL * 0.35, ys - bhR * 0.5, zc + bhR * 0.9], [xbh - bhL * 0.35, ys - bhR * 0.5, zc + bhR + 0.07], 0.008, 8)\n  k.cyl('stainless', 0.03, 0.03, [xbh - bhL * 0.35, ys - bhR * 0.5 + 0.015, zc + bhR + 0.08], 'y', 16)\n  k.cyl('glass', 0.032, 0.09, [xbh - bhL * 0.35, ys - bhR * 0.5 + 0.075, zc + bhR + 0.08], 'y', 16)\n  k.dome('glass', 0.032, [xbh - bhL * 0.35, ys - bhR * 0.5 + 0.12, zc + bhR + 0.08], 'y', Math.PI / 2, 1)\n  k.cyl('brass', 0.01, 0.04, [xbh - bhL * 0.6, ys + bhR + 0.02, zc], 'y', 8)\n  k.sphere('brass', 0.018, [xbh - bhL * 0.6, ys + bhR + 0.045, zc], undefined, 10)\n  // bearing-housing support foot (OH2)\n  k.box(C, [0.05, ys - hb - bhR * 0.6, bhR * 1.2], [xbh - bhL * 0.55, (ys - bhR * 0.6 + hb) / 2, zc])\n  k.box('flangeSteel', [0.12, 0.02, bhR * 1.5], [xbh - bhL * 0.55, hb + 0.01, zc])\n  // vibration probes on bearing housing\n  for (const a of [Math.PI / 4, (3 * Math.PI) / 4]) {\n    const p: V3 = [xbh - bhL * 0.25, ys + Math.sin(a) * bhR, zc + Math.cos(a) * bhR]\n    k.rod('stainless', p, [p[0], ys + Math.sin(a) * (bhR + 0.06), zc + Math.cos(a) * (bhR + 0.06)], 0.012, 10)\n  }\n  const shaftR = Math.max(0.025, bhR * 0.28)\n  const xShaftEnd = xbh - bhL - 0.12\n  k.cyl('stainless', shaftR, 0.12, [xShaftEnd + 0.06, ys, zc], 'x', 16)\n\n  // centreline-mounted casing feet + pedestals\n  const zFoot = Rend + r1 * 0.6\n  for (const s of [-1, 1]) {\n    k.box(C, [0.1, 0.05, zFoot - Rhub * 0.7], [xc, ys, zc + s * (Rhub * 0.7 + (zFoot - Rhub * 0.7) / 2)])\n    k.box(C, [0.16, 0.035, 0.12], [xc, ys - 0.04, zc + s * zFoot])\n    k.box(BASE, [0.14, ys - 0.06 - hb, 0.11], [xc, (ys - 0.06 + hb) / 2, zc + s * zFoot])\n    k.box(BASE, [0.2, 0.02, 0.17], [xc, hb + 0.01, zc + s * zFoot])\n    k.box('stainless', [0.17, 0.006, 0.13], [xc, ys - 0.06 + 0.003 - 0.0, zc + s * zFoot])\n    for (const dx of [-0.045, 0.045]) k.hex('nut', 0.03, 0.02, [xc + dx, ys - 0.012, zc + s * zFoot])\n  }\n\n  // coupling + motor\n  const coupL = 0.26\n  const xCoupEnd = xShaftEnd\n  const xMotorShaft = xCoupEnd - coupL\n  const finFrac = 0.12\n  let Rm = 0.12 + 0.03 * Math.pow(Math.max(p.kw, 1), 0.4)\n  Rm = Math.min(Rm, (ys - hb - 0.04) / (1 + finFrac) - 0.02, BW / 2 - 0.12)\n  const stub = 0.1 + Rm * 0.2\n  const xm = xMotorShaft - stub + 0.02 // motor local origin (DE shield face)\n  const cowl = Math.max(0.12, Rm * 0.6)\n  const Lwant = 2.6 * Rm + 0.35\n  const Lm = Math.max(0.5, Math.min(Lwant, xm - (-BL / 2 + 0.06) - cowl))\n  k.at([xm, ys, zc], undefined, () => motor(k, { R: Rm, L: Lm, H: ys - hb, tbox: 'side', cable: true }))\n  // motor sole plates (shims) visible under feet already; coupling guard with the coupling (and\n  // shaft ends) turning inside it, and the motor's cooling fan behind the cowl grille, all on one shaft\n  const rpm = 165\n  const rotors: RotorSpec[] = [\n    guardedCoupling(k, xMotorShaft - 0.02, xCoupEnd + 0.04, ys, Math.max(0.16, shaftR * 4.2), hb, 'safetyYellow', rpm, zc, Math.max(shaftR, 0.02, Rm * 0.13)),\n    cowlFan(Rm, Lm, rpm, [xm, ys, zc]),\n  ]\n\n  // baseplate (drain-rim, grout holes, anchor bolts)\n  const t = 0.012\n  // bottom flange (anchor flange) wider than the frame\n  k.box(BASE, [BL, 0.016, BW + 0.08], [0, 0.008, 0])\n  // side + end channels\n  for (const s of [-1, 1]) {\n    k.box(BASE, [BL, hb - 0.016, t], [0, hb / 2 + 0.008, s * (BW / 2 - t / 2)])\n    k.box(BASE, [t, hb - 0.016, BW], [s * (BL / 2 - t / 2), hb / 2 + 0.008, 0])\n    // stiffeners on the side channels\n    for (let i = 1; i < Math.round(BL / 0.45); i++) {\n      const xx = -BL / 2 + (i * BL) / Math.round(BL / 0.45)\n      k.box(BASE, [0.01, hb - 0.03, 0.04], [xx, hb / 2 + 0.008, s * (BW / 2 + 0.02)])\n    }\n  }\n  // drain-rim deck: top plate slightly below the rim, rim lip around the edge\n  k.box(BASE, [BL - 2 * t, 0.01, BW - 2 * t], [0, hb - 0.03, 0])\n  for (const s of [-1, 1]) {\n    k.box(BASE, [BL, 0.045, 0.02], [0, hb - 0.005, s * (BW / 2 - 0.01)])\n    k.box(BASE, [0.02, 0.045, BW], [s * (BL / 2 - 0.01), hb - 0.005, 0])\n  }\n  // longitudinal sub-beams under pump/motor pads\n  for (const s of [-1, 1]) k.box(BASE, [BL - 0.1, hb - 0.05, 0.12], [0, (hb - 0.05) / 2 + 0.016, s * BW * 0.28])\n  // motor pads + jack-screw blocks\n  for (const fx of [xm - Lm * 0.82, xm - Lm * 0.18])\n    for (const s of [-1, 1]) {\n      k.box('flangeSteel', [0.22, 0.025, 0.2], [fx, hb - 0.012, zc + s * Rm * 0.78])\n      k.box(BASE, [0.04, 0.06, 0.06], [fx + 0.16, hb + 0.02, zc + s * Rm * 0.78])\n      k.cyl('boltXylan', 0.008, 0.08, [fx + 0.14, hb + 0.03, zc + s * Rm * 0.78], 'x', 6)\n    }\n  // grout / vent holes along the centreline\n  const nG = Math.max(2, Math.round(BL / 0.9))\n  for (let i = 0; i < nG; i++) {\n    const gx = -BL / 2 + (BL * (i + 0.5)) / nG\n    for (const gz of [-BW * 0.12, BW * 0.12]) {\n      k.torus(BASE, 0.05, 0.008, [gx, hb - 0.022, gz], 'y', 6, 24)\n      k.disc('rubberBlack', 0.048, [gx, hb - 0.0245, gz], 'y', 24)\n    }\n  }\n  // anchor bolts with washers + nuts along both long sides and ends\n  const nA = Math.max(3, Math.round(BL / 0.7) + 1)\n  for (let i = 0; i < nA; i++) {\n    const ax = -BL / 2 + 0.08 + ((BL - 0.16) * i) / (nA - 1)\n    for (const s of [-1, 1]) {\n      const az = s * (BW / 2 + 0.035)\n      k.cyl('stainless', 0.02, 0.004, [ax, 0.018, az], 'y', 16)\n      k.hex('nut', 0.042, 0.024, [ax, 0.032, az])\n      k.cyl('boltXylan', 0.011, 0.07, [ax, 0.05, az], 'y', 8)\n    }\n  }\n  // lifting lugs at the 4 corners\n  for (const sx of [-1, 1])\n    for (const sz of [-1, 1]) {\n      k.box(BASE, [0.1, 0.1, 0.02], [sx * (BL / 2 - 0.12), hb * 0.55, sz * (BW / 2 + 0.01)])\n      k.torus('nut', 0.025, 0.006, [sx * (BL / 2 - 0.12), hb * 0.55 + 0.02, sz * (BW / 2 + 0.022)], 'z', 6, 14)\n    }\n  // drain connection at the low corner\n  k.cyl(BASE, 0.022, 0.12, [BL / 2 + 0.05, hb - 0.04, -BW / 2 + 0.15], 'x', 12)\n  k.hex('nut', 0.05, 0.03, [BL / 2 + 0.11, hb - 0.04, -BW / 2 + 0.15], 'x')\n  // earth boss + tag sign\n  k.cyl('brass', 0.015, 0.03, [-BL / 2 + 0.3, hb * 0.5, BW / 2 + 0.015], 'z', 10)\n  if (p.tag) tagPlate(k, p.tag, [-BL / 2 + 0.6, hb * 0.5, BW / 2 + 0.002], 'z', 0.22)\n\n  // API Plan 53A seal reservoir\n  const px = Math.min(xbh - bhL * 0.2, xc - 0.25)\n  const pz = -(BW / 2 - 0.12)\n  const vy0 = Math.max(ys + 0.25, 0.95)\n  const vH = 0.5\n  const vr = 0.075\n  k.rod('structSteel', [px, hb, pz], [px, vy0 - 0.05, pz], 0.025, 10)\n  k.box('structSteel', [0.16, 0.012, 0.16], [px, hb + 0.006, pz])\n  k.box('structSteel', [0.2, 0.012, 0.2], [px, vy0 - 0.05, pz])\n  k.cyl('stainless', vr, vH, [px, vy0 + vH / 2, pz], 'y', 28)\n  k.dome('stainless', vr, [px, vy0 + vH, pz], 'y', Math.PI / 2, 0.5)\n  k.dome('stainless', vr, [px, vy0, pz], '-y', Math.PI / 2, 0.5)\n  for (const s of [-1, 1]) k.box('structSteel', [0.03, 0.08, 0.012], [px + s * (vr + 0.012), vy0 - 0.02, pz])\n  // level transmitter chamber on reservoir + PI gauge + N2 inlet + fill cap\n  k.cyl('stainless', 0.018, vH * 0.7, [px + vr + 0.06, vy0 + vH / 2, pz], 'y', 12)\n  for (const yy of [vy0 + vH * 0.2, vy0 + vH * 0.8]) k.rod('tubing', [px + vr * 0.9, yy, pz], [px + vr + 0.06, yy, pz], 0.006, 8)\n  k.rbox('actuatorBlue', [0.06, 0.08, 0.06], 0.01, [px + vr + 0.06, vy0 + vH * 0.85 + 0.06, pz])\n  k.cyl('stainless', 0.008, 0.08, [px, vy0 + vH + 0.07, pz], 'y', 8)\n  gauge(k, [px, vy0 + vH + 0.14, pz], 'z', 0.08, 'bar', 60)\n  k.cyl('brass', 0.016, 0.05, [px - 0.035, vy0 + vH + 0.02, pz], 'y', 10)\n  k.rod('tubing', [px + 0.03, vy0 + vH + 0.02, pz], [px + 0.03, vy0 + vH + 0.1, pz + 0.1], 0.005, 6)\n  // seal supply / return tubing between gland and reservoir\n  k.tube('tubing', [[portIn[0], portIn[1], portIn[2]], [portIn[0], portIn[1] + 0.06, portIn[2]], [px, portIn[1] + 0.06, portIn[2]], [px, portIn[1] + 0.06, pz + vr + 0.08], [px, vy0 + 0.05, pz + vr + 0.08], [px, vy0 + 0.05, pz + vr * 0.7]], 0.006)\n  k.tube('tubing', [[portOut[0], portOut[1], portOut[2]], [portOut[0], portOut[1] + 0.12, portOut[2]], [px + 0.04, portOut[1] + 0.12, portOut[2]], [px + 0.04, portOut[1] + 0.12, pz + vr + 0.12], [px + 0.04, vy0 + vH * 0.7, pz + vr + 0.12], [px + 0.04, vy0 + vH * 0.7, pz + vr * 0.6]], 0.006)\n\n  // local control station\n  const lx = -BL / 2 + 0.14\n  const lz = BW / 2 - 0.14\n  k.rod('structSteel', [lx, hb, lz], [lx, 1.45, lz], 0.03, 12)\n  k.box('structSteel', [0.15, 0.012, 0.15], [lx, hb + 0.006, lz])\n  const by = 1.2\n  k.box('structSteel', [0.04, 0.3, 0.02], [lx, by, lz + 0.04])\n  k.rbox('stainless', [0.24, 0.32, 0.13], 0.012, [lx, by, lz + 0.115])\n  k.box('stainless', [0.3, 0.012, 0.2], [lx, by + 0.18, lz + 0.12], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), 0.15))\n  const fz = lz + 0.18\n  k.cyl('safetyGreen', 0.016, 0.02, [lx - 0.055, by + 0.05, fz], 'z', 16)\n  k.cyl('actuatorRed', 0.016, 0.02, [lx + 0.055, by + 0.05, fz], 'z', 16)\n  k.cyl('actuatorYellow', 0.04, 0.004, [lx, by - 0.07, fz - 0.002], 'z', 24)\n  k.cyl('actuatorRed', 0.012, 0.03, [lx, by - 0.07, fz + 0.012], 'z', 12)\n  k.dome('actuatorRed', 0.028, [lx, by - 0.07, fz + 0.025], 'z', Math.PI / 2, 0.6)\n  gauge(k, [lx, by + 0.11, fz - 0.01], 'z', 0.06, 'bar', 600)\n  k.hex('brass', 0.03, 0.02, [lx, by - 0.17, lz + 0.11])\n  k.spline('cable', [[lx, by - 0.18, lz + 0.11], [lx, by - 0.35, lz + 0.06], [lx, hb + 0.2, lz + 0.04], [lx + 0.1, hb + 0.02, lz]], 0.008, 6, 24)\n\n  return { built: k.build(), rotors }\n}\n\n\n"
    }
  ],
  "categories": [
    "rotating"
  ],
  "meta": {
    "export": "CentrifugalPump",
    "anchor": "base",
    "standards": [
      "API 610 (OH2)",
      "API 682 Plan 53A",
      "IEC 60034 / 60072",
      "ASME B16.5"
    ]
  }
}