{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "vertical-inline-pump",
  "type": "registry:component",
  "title": "Vertical inline pump (API 610 OH3)",
  "description": "Overhung vertical inline pump with its own bearing bracket: horizontal volute casing with suction and discharge flanges on one centreline (X axis), casing foot on an anchored soleplate, casing vent and drain valves, cartridge seal with Plan 11 flush, grease-lubricated bearing housing with nipples and RTD, spacer coupling in a guarded motor stool and a vertical 2-pole IEC motor with drip canopy (cable entry to the back). Compact footprint; nozzle sizes and kW drive the geometry.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "rotating/vertical-inline-pump.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/rotating/vertical-inline-pump.tsx",
      "content": "// API 610 OH3 vertical inline pump: horizontal volute casing with in-line suction and discharge, bearing bracket, guarded coupling and a vertical IEC motor on top.\nimport * as THREE from 'three'\nimport { useEffect, useMemo } from 'react'\nimport { Kit, type MatKey } from '../lib/kit'\nimport { KitMesh, type GroupProps } from '../lib/mesh'\nimport { tagPlate } from '../lib/parts'\nimport { port, type Port } from '../lib/ports'\nimport { Rotors, type RotorSpec } from '../lib/rotors'\nimport { flange as flangeDims, pipeOD, type Rating } from '../lib/standards'\nimport { TAU, anchorBolt, annulus, castExtrude, clamp, flangeLen, flangedBranch, meshTube, motorEnvelope, pipeGauge, smallValve, strut, verticalMotor, voluteShape } from './_pumps'\n\nexport interface VerticalInlinePumpProps {\n  suctionNps?: number\n  dischargeNps?: number\n  rating?: Rating\n  /** motor rated power, kW */\n  kw?: number\n  casing?: MatKey\n  tag?: string\n  running?: boolean\n}\n\nexport const defaults = {\n  suctionNps: 4,\n  dischargeNps: 4,\n  rating: 300 as Rating,\n  kw: 55,\n  casing: 'machineryGreen' as MatKey,\n  running: true,\n}\n\ntype P = Omit<typeof defaults, 'running'> & { tag?: string }\n\nfunction layout(p: P) {\n  const dOD = pipeOD(p.dischargeNps)\n  const r0 = 0.1 + 0.02 * p.dischargeNps\n  const r1 = r0 + dOD * 0.85\n  const wv = Math.max(dOD, pipeOD(p.suctionNps)) * 1.25\n  const zs = -(r0 + r1) / 2 // shaft axis z, so the volute outlet lands on z = 0\n  const fOD = Math.max(flangeDims(p.suctionNps, p.rating).od, flangeDims(p.dischargeNps, p.rating).od)\n  const yN = Math.max(0.42, fOD / 2 + 0.2)\n  const reach = r1 * 0.85 + 0.08\n  const xS = -(reach + flangeLen(p.suctionNps, p.rating))\n  const xD = reach + flangeLen(p.dischargeNps, p.rating)\n  return { dOD, r0, r1, wv, zs, yN, xS, xD }\n}\n\nexport function ports(props: VerticalInlinePumpProps = {}): Port[] {\n  const p = { ...defaults, ...props }\n  const L = layout(p)\n  return [\n    port('suction', 'Suction', [L.xS, L.yN, 0], [-1, 0, 0], p.suctionNps, p.rating),\n    port('discharge', 'Discharge', [L.xD, L.yN, 0], [1, 0, 0], p.dischargeNps, p.rating),\n  ]\n}\n\nexport function VerticalInlinePump({ tag, ...rest }: VerticalInlinePumpProps & GroupProps) {\n  const { suctionNps, dischargeNps, rating, kw, casing, running, ...group } = { ...defaults, ...rest }\n  const { built, rotors } = useMemo(() => build({ suctionNps, dischargeNps, rating, kw, casing, tag }), [suctionNps, dischargeNps, rating, kw, casing, tag])\n  useEffect(() => () => [built, ...rotors.map((r) => r.built)].forEach((b) => b.geometry.dispose()), [built, rotors])\n  return (\n    <group {...group}>\n      <KitMesh data={built} />\n      <Rotors rotors={rotors} running={running} />\n    </group>\n  )\n}\n\nexport default VerticalInlinePump\n\nfunction build(p: P) {\n  const k = new Kit()\n  const C = p.casing\n  const { r0, r1, wv, zs, yN, xS, xD } = layout(p)\n  const m = motorEnvelope(p.kw, 2)\n  const Rm = clamp(m.R, 0.08, 0.6)\n  const Hm = clamp(m.L, 0.25, 2)\n  const rs = Math.max(0.02, m.shaftDia * 0.55)\n\n  // volute scroll in the XZ plane around the shaft (x = 0, z = zs), outlet heading +X on z = 0\n  const vm = new THREE.Matrix4().makeBasis(new THREE.Vector3(0, 0, 1), new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0)).setPosition(0, yN, zs)\n  castExtrude(k, C, voluteShape(r0, r1, r1 * 0.5), wv, Math.min(0.03, wv * 0.15), vm, 16)\n  flangedBranch(k, C, [r1 * 0.4, yN, 0], [xD, yN, 0], p.dischargeNps, p.rating, '80')\n  // suction passage: neck from the flange into a bowl under the impeller eye\n  const sOD = pipeOD(p.suctionNps)\n  flangedBranch(k, C, [-r1 * 0.3, yN, 0], [xS, yN, 0], p.suctionNps, p.rating, '80')\n  k.lathe(C, [[0, yN - wv / 2 - sOD * 0.5], [r0 * 0.8, yN - wv / 2 - sOD * 0.45], [r0 * 1.05, yN - wv * 0.3], [r0 * 1.05, yN], [0, yN]], [0, 0, zs], 'y', 36)\n  k.rod(C, [-r1 * 0.5, yN, 0], [0, yN, zs * 0.6], sOD * 0.55, 24)\n  // casing foot on a soleplate with anchor bolts\n  const yFoot = yN - wv / 2 - sOD * 0.5\n  const fr = Math.max(0.12, r0 * 0.8)\n  k.lathe(C, [[fr * 0.7, 0.04], [fr, 0.06], [r0 * 0.6, yFoot + 0.01], [0, yFoot + 0.01]], [0, 0, zs], 'y', 32)\n  annulus(k, C, 0, fr * 1.15, 0.04, 0.07, [0, 0, zs], 'y', 32)\n  const sp = Math.max(0.5, fr * 2.6)\n  k.box('equipmentPaintDark', [sp, 0.04, sp], [0, 0.02, zs])\n  for (const sx of [-1, 1]) for (const sz of [-1, 1]) anchorBolt(k, [sx * (sp / 2 - 0.05), 0.04, zs + sz * (sp / 2 - 0.05)], 0.018)\n  for (let i = 0; i < 4; i++) {\n    const a = (i / 4) * TAU + Math.PI / 4\n    k.hex('nut', 0.034, 0.02, [Math.cos(a) * fr * 1.0, 0.08, zs + Math.sin(a) * fr * 1.0])\n  }\n  // casing drain valve + plug, vent valve on the cover, gauges on both nozzles\n  k.rod(C, [0, yFoot + 0.05, zs + r0 * 0.6], [0, yFoot + 0.05, zs + r0 * 0.6 + 0.12], 0.014, 8)\n  smallValve(k, [0, yFoot + 0.05, zs + r0 * 0.6 + 0.15], 0.7, 'z')\n  k.hex('nut', 0.03, 0.02, [0, yFoot + 0.05, zs + r0 * 0.6 + 0.24], 'z')\n  pipeGauge(k, [xS * 0.55, yN + sOD / 2, 0], [0, 0, 1], 25)\n  pipeGauge(k, [xD * 0.55, yN + pipeOD(p.dischargeNps) / 2, 0], [0, 0, 1], 60)\n\n  // casing cover with stud ring, seal chamber + gland with Plan 11 flush line\n  const yC0 = yN + wv / 2\n  const Rcov = r0 * 0.95\n  annulus(k, C, 0, Rcov, yC0 - 0.01, yC0 + 0.04, [0, 0, zs], 'y', 40)\n  k.boltCircle('boltXylan', 'nut', 10, Rcov - 0.022, [0, yC0 - 0.02, zs], 'y', 0.014, 0.08, [0.07])\n  k.cyl(C, 0.012, 0.05, [-Rcov * 0.75, yC0 + 0.065, zs], 'y', 8)\n  smallValve(k, [-Rcov * 0.75, yC0 + 0.1, zs], 0.6)\n  const sealR = Math.max(0.07, r0 * 0.45)\n  const yS0 = yC0 + 0.04\n  k.cyl(C, sealR, 0.12, [0, yS0 + 0.06, zs], 'y', 28)\n  k.cyl('stainless', sealR * 1.1, 0.03, [0, yS0 + 0.135, zs], 'y', 24)\n  k.boltCircle('boltXylan', 'nut', 4, sealR * 0.92, [0, yS0 + 0.1, zs], 'y', 0.012, 0.07, [0.06], Math.PI / 4)\n  k.tube('tubing', [[xD * 0.45, yN + pipeOD(p.dischargeNps) / 2, 0], [xD * 0.45, yS0 + 0.135, 0], [sealR * 1.1, yS0 + 0.135, zs * 0.5], [sealR * 0.8, yS0 + 0.135, zs]], 0.007)\n  // bearing bracket: lantern with windows, bearing housing, oiler\n  const yB0 = yS0 + 0.15\n  const hBr = clamp(0.22 + Rm * 0.3, 0.25, 0.45)\n  const bhR = Math.max(0.08, Rm * 0.45, sealR * 1.1)\n  const rL = Math.max(bhR * 1.15, r0 * 0.7)\n  annulus(k, C, rs * 1.2, rL * 1.1, yB0, yB0 + 0.03, [0, 0, zs], 'y', 32)\n  for (let i = 0; i < 3; i++) {\n    const a = (i / 3) * TAU + Math.PI / 2\n    const u = [Math.cos(a), Math.sin(a)]\n    strut(k, C, [u[0] * rL, yB0 + 0.03, zs + u[1] * rL], [u[0] * bhR, yB0 + hBr * 0.5, zs + u[1] * bhR], 0.07, 0.025, [-u[1], 0, u[0]])\n  }\n  k.lathe(C, [[bhR * 0.8, yB0 + hBr * 0.45], [bhR, yB0 + hBr * 0.5], [bhR, yB0 + hBr], [bhR * 1.2, yB0 + hBr], [bhR * 1.2, yB0 + hBr + 0.04]], [0, 0, zs], 'y', 32)\n  for (let i = 0; i < 4; i++) annulus(k, C, bhR, bhR * 1.1, yB0 + hBr * (0.6 + i * 0.1), yB0 + hBr * (0.6 + i * 0.1) + 0.008, [0, 0, zs], 'y', 32)\n  // grease-lubricated bearings: nipples top and bottom, relief plugs; RTD head\n  for (const yy of [yB0 + hBr * 0.62, yB0 + hBr * 0.92]) {\n    k.cyl('brass', 0.007, 0.03, [0, yy, zs + bhR * 1.1 + 0.015], 'z', 8)\n    k.sphere('brass', 0.01, [0, yy, zs + bhR * 1.1 + 0.032], undefined, 8)\n  }\n  k.hex('nut', 0.02, 0.012, [bhR * 1.1 + 0.004, yB0 + hBr * 0.62, zs], 'x')\n  k.cyl('stainless', 0.007, 0.05, [-bhR * 1.1 - 0.02, yB0 + hBr * 0.8, zs], 'x', 8)\n  k.cyl('stainless', 0.018, 0.03, [-bhR * 1.1 - 0.055, yB0 + hBr * 0.8, zs], 'x', 12)\n  // motor adapter (stool) with mesh windows around the coupling\n  const yA0 = yB0 + hBr + 0.04\n  const hA = clamp(0.24 + Rm * 0.35, 0.28, 0.6)\n  const yA1 = yA0 + hA\n  const rA0 = bhR * 1.2\n  const rA1 = Math.max(Rm * 0.9, rA0)\n  for (let i = 0; i < 4; i++) {\n    const a = (i / 4) * TAU + Math.PI / 4\n    const u = [Math.cos(a), Math.sin(a)]\n    strut(k, C, [u[0] * (rA0 - 0.01), yA0, zs + u[1] * (rA0 - 0.01)], [u[0] * (rA1 - 0.02), yA1 - 0.03, zs + u[1] * (rA1 - 0.02)], Math.max(0.06, Rm * 0.25), 0.02, [-u[1], 0, u[0]])\n  }\n  annulus(k, C, rA1 - 0.06, rA1 + 0.04, yA1 - 0.035, yA1, [0, 0, zs], 'y', 40)\n  k.at([0, 0, zs], undefined, () => meshTube(k, [0, yA0, 0], [0, yA1 - 0.035, 0], rA0 + 0.008, rA1 - 0.006))\n  // spacer coupling + shaft ends\n  const yCp = (yA0 + yA1) / 2\n  const r = new Kit()\n  r.cyl('flangeSteel', rs, hA + 0.08, [0, 0, 0], 'y', 12)\n  for (const s of [-1, 1]) {\n    r.cyl('flangeSteel', rs * 2.4, hA * 0.22, [0, s * hA * 0.3, 0], 'y', 20)\n    r.cyl('nut', rs * 2.9, 0.012, [0, s * hA * 0.17, 0], 'y', 20)\n    r.box('nut', [rs * 0.5, hA * 0.2, rs * 0.5], [rs * 2.4, s * hA * 0.3, 0])\n  }\n  const rotors: RotorSpec[] = [{ built: r.build(), axis: 'y', pos: [0, yCp, zs], rpm: 160 }]\n\n  k.at([0, 0, zs], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI), () => verticalMotor(k, { R: Rm, H: Hm, yF: yA1, plate: `IEC ${m.frame} ${p.kw} kW 2P`, deck: 0.04 }))\n  if (p.tag) tagPlate(k, p.tag, [0, yN + wv * 0.15, zs + r1 + 0.003], 'z', 0.14)\n  return { built: k.build(), rotors }\n}\n"
    },
    {
      "path": "rotating/_pumps.ts",
      "type": "registry:file",
      "target": "components/deck-twin/rotating/_pumps.ts",
      "content": "// Shared pump, motor and fan geometry: plates, guards, vertical motor, drain-rim baseplate, flanged branches, control stations.\nimport * as THREE from 'three'\nimport { Kit, mat4, uvScale, type MatKey, type Orient, type V3 } from '../lib/kit'\nimport { flange, gauge, motorSize, tagPlate } from '../lib/parts'\nimport { fanGuardMaterial, meshGuardMaterial } from '../lib/textures'\nimport { flange as flangeDims, pipeOD, pipeWall, type Rating } from '../lib/standards'\n\nexport const TAU = Math.PI * 2\nexport const clamp = (v: number, a: number, b: number) => Math.min(b, Math.max(a, v))\n\n/** Quaternion turning local +Z onto n (plates facing n). */\nexport const faceQ = (n: V3) => new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), new THREE.Vector3(...n).normalize())\n\n/** Flat ring (rIn = 0: disc) between y0 and y1 along `axis` from `pos`. */\nexport function annulus(k: Kit, mat: MatKey, rIn: number, rOut: number, y0: number, y1: number, pos: V3 = [0, 0, 0], axis: Orient = 'y', seg = 40) {\n  k.lathe(mat, [[rIn, y0], [rOut, y0], [rOut, y1], [rIn, y1], [rIn, y0]], pos, axis, seg)\n}\n\n/** Open (uncapped) cylinder between two points. */\nexport function openTube(k: Kit, mat: MatKey, a: V3, b: V3, r: number, seg = 24, r2?: number) {\n  const va = new THREE.Vector3(...a)\n  const d = new THREE.Vector3(...b).sub(va)\n  const L = d.length()\n  if (L < 1e-6) return\n  const mid = va.addScaledVector(d, 0.5)\n  k.add(mat, new THREE.CylinderGeometry(r2 ?? r, r, L, seg, 1, true), mat4([mid.x, mid.y, mid.z], d.toArray() as V3))\n}\n\n/** Expanded-metal guard tube between two points. */\nexport function meshTube(k: Kit, a: V3, b: V3, r: number, r2?: number, color?: string) {\n  const va = new THREE.Vector3(...a)\n  const d = new THREE.Vector3(...b).sub(va)\n  const L = d.length()\n  const g = new THREE.CylinderGeometry(r2 ?? r, r, L, 28, 1, true)\n  uvScale(g, TAU * Math.max(r, r2 ?? r) * 8, L * 8)\n  const mid = va.addScaledVector(d, 0.5)\n  k.add(meshGuardMaterial(color), g, mat4([mid.x, mid.y, mid.z], d.toArray() as V3))\n}\n\n/** Expanded-metal flat panel w × h facing +normal. */\nexport function meshPanel(k: Kit, w: number, h: number, pos: V3, normal: V3, color?: string) {\n  const g = new THREE.PlaneGeometry(w, h)\n  uvScale(g, w * 8, h * 8)\n  const m = new THREE.Matrix4().compose(new THREE.Vector3(...pos), faceQ(normal), new THREE.Vector3(1, 1, 1))\n  k.add(meshGuardMaterial(color), g, m)\n}\n\n/** Plate from a to b: width w along `side` (made perpendicular), thickness t. */\nexport function strut(k: Kit, mat: MatKey, a: V3, b: V3, w: number, t: number, side: V3) {\n  const A = new THREE.Vector3(...a)\n  const Y = new THREE.Vector3(...b).sub(A)\n  const L = Y.length()\n  if (L < 1e-6) return\n  Y.normalize()\n  const X = new THREE.Vector3(...side)\n  X.addScaledVector(Y, -X.dot(Y)).normalize()\n  const Z = new THREE.Vector3().crossVectors(X, Y)\n  const m = new THREE.Matrix4().makeBasis(X, Y, Z).setPosition(A.addScaledVector(Y, L / 2))\n  k.add(mat, new THREE.BoxGeometry(w, L, t), m)\n}\n\n/** Triangular gusset in the radial plane at angle `ang` about +Y, r0 → r1 at yPlate, height h (h < 0 hangs below). */\nexport function gusset(k: Kit, mat: MatKey, ang: number, r0: number, r1: number, yPlate: number, h: number, t: number) {\n  const s = new THREE.Shape()\n  s.moveTo(r0, yPlate)\n  s.lineTo(r1, yPlate)\n  s.lineTo(r0, yPlate + h)\n  s.closePath()\n  const m = new THREE.Matrix4().makeRotationY(ang).multiply(new THREE.Matrix4().makeTranslation(0, 0, -t / 2))\n  k.extrude(mat, s, t, m, 1)\n}\n\n/** Toothed (cooling-fin) TEFC frame section in the XY plane. */\nexport function finnedShape(R: number, finH: number, nFin: number) {\n  const sh = new THREE.Shape()\n  const step = TAU / nFin\n  const finW = Math.min(0.012, step * R * 0.28)\n  const da = finW / 2 / R\n  const pts: [number, number][] = []\n  for (let i = 0; i < nFin; i++) {\n    const a = i * step\n    for (let s = 0; s < 3; s++) {\n      const aa = a - step / 2 + (s / 3) * (step / 2 - da)\n      pts.push([Math.cos(aa) * R, Math.sin(aa) * R])\n    }\n    pts.push([Math.cos(a - da) * R, Math.sin(a - da) * R])\n    pts.push([Math.cos(a - da * 0.6) * (R + finH), Math.sin(a - da * 0.6) * (R + finH)])\n    pts.push([Math.cos(a + da * 0.6) * (R + finH), Math.sin(a + da * 0.6) * (R + finH)])\n    pts.push([Math.cos(a + da) * R, Math.sin(a + da) * R])\n  }\n  pts.forEach(([x, y], i) => (i ? sh.lineTo(x, y) : sh.moveTo(x, y)))\n  sh.closePath()\n  return sh\n}\n\n/** Weld-neck flange length (weld end to raised face). */\nexport const flangeLen = (nps: number, rating: Rating) => {\n  const f = flangeDims(nps, rating)\n  return f.hubLength + f.raisedFaceHeight\n}\n\n/** Branch pipe from `from` along `dir` to a WN flange (with studs) whose raised face is at `face`. */\nexport function flangedBranch(k: Kit, mat: MatKey, from: V3, face: V3, nps: number, rating: Rating, schedule: '40' | '80' | 'STD' = '80') {\n  const d = new THREE.Vector3(...face).sub(new THREE.Vector3(...from)).normalize()\n  const L = flange(k, nps, rating, face, d.toArray() as V3, true)\n  const end = new THREE.Vector3(...face).addScaledVector(d, -L).toArray() as V3\n  const ro = pipeOD(nps) / 2\n  k.pipe(mat, from, end, pipeOD(nps), pipeWall(nps, schedule), nps >= 6 ? 32 : 20)\n  // bore through the flange hub up to the face, and a dark end where the branch opens into the casing\n  openTube(k, 'pipeBore', end, new THREE.Vector3(...face).addScaledVector(d, -0.0005).toArray() as V3, ro * 0.88, nps >= 6 ? 32 : 20)\n  k.disc('pipeBore', ro - pipeWall(nps, schedule) + 0.001, from, d.toArray() as V3, 24)\n}\n\n/** IEC motor envelope from kW; above the IEC 60072 range (1 MW) the frame grows with the cube root of power. */\nexport function motorEnvelope(kw: number, poles: 2 | 4 | 6 = 4) {\n  const m = motorSize(kw, poles)\n  const s = kw > 1000 ? Math.cbrt(kw / 1000) : 1\n  return { ...m, R: m.R * s, L: m.L * s, H: m.H * s, frame: kw > 1000 ? 'HV' : m.frame }\n}\n\nexport interface VerticalMotorOpts {\n  /** frame radius (without fins), m */\n  R: number\n  /** frame length flange to fan end, m */\n  H: number\n  /** D-flange underside height, m */\n  yF: number\n  mat?: MatKey\n  /** text on the nameplate, e.g. \"IEC 315M 160 kW\" */\n  plate?: string\n  /** height of the deck the cables run down to */\n  deck?: number\n  canopy?: boolean\n}\n\n/** Vertical TEFC motor (IM V1): D-flange, finned frame, end shields, fan cowl, drip canopy, terminal + heater boxes. Returns the top height. */\nexport function verticalMotor(k: Kit, o: VerticalMotorOpts) {\n  const { R: Rm, H: Hm, yF } = o\n  const drv = o.mat ?? 'motorPaint'\n  const deck = o.deck ?? 0\n  const finH = Math.max(0.012, Rm * 0.1)\n  const shL = Math.max(0.05, Rm * 0.22)\n  annulus(k, drv, 0, Rm * 1.12, yF, yF + 0.05, [0, 0, 0], 'y', 48)\n  k.boltCircle('boltXylan', 'nut', 8, Rm * 1.04, [0, yF - 0.04, 0], 'y', Math.max(0.012, Rm * 0.04), 0.12, [0.105], Math.PI / 8)\n  const y0f = yF + 0.05 + shL\n  const yM1 = yF + 0.05 + Hm\n  const y1f = yM1 - shL\n  k.lathe(drv, [[Rm * 0.6, yF + 0.05], [Rm + finH * 0.6, yF + 0.05 + shL * 0.55], [Rm + finH * 0.6, y0f]], [0, 0, 0], 'y', 48)\n  const nFin = Math.max(28, Math.round(Rm * 100))\n  k.extrude(drv, finnedShape(Rm, finH, nFin), y1f - y0f, new THREE.Matrix4().makeTranslation(0, y0f, 0).multiply(new THREE.Matrix4().makeRotationX(-Math.PI / 2)), 2)\n  k.lathe(drv, [[Rm + finH * 0.6, y1f], [Rm + finH * 0.6, y1f + shL * 0.4], [Rm * 0.7, yM1]], [0, 0, 0], 'y', 48)\n  k.boltCircle('nut', 'nut', 8, Rm + finH * 0.3, [0, yF + 0.05 + shL * 0.7, 0], 'y', Math.max(0.008, Rm * 0.03), 0.03, [0.02])\n  const cR = Rm + finH + 0.015\n  const cowlH = Math.max(0.12, Rm * 0.45)\n  k.lathe(drv, [\n    [cR - 0.005, y1f - shL * 0.3],\n    [cR, y1f - shL * 0.3],\n    [cR, yM1 + cowlH * 0.8],\n    [cR * 0.94, yM1 + cowlH],\n    [cR * 0.94 - 0.004, yM1 + cowlH],\n  ], [0, 0, 0], 'y', 48)\n  k.disc('rubberBlack', cR * 0.9, [0, yM1 + cowlH - 0.02, 0], 'y', 32)\n  k.disc(fanGuardMaterial(), cR * 0.92, [0, yM1 + cowlH + 0.001, 0], 'y', 32)\n  let top = yM1 + cowlH\n  if (o.canopy !== false) {\n    const yCan = yM1 + cowlH + Math.max(0.06, Rm * 0.14)\n    for (let i = 0; i < 4; i++) {\n      const a = (i / 4) * TAU\n      k.rod(drv, [Math.cos(a) * cR * 0.88, yM1 + cowlH, Math.sin(a) * cR * 0.88], [Math.cos(a) * cR * 0.88, yCan, Math.sin(a) * cR * 0.88], 0.01, 6)\n    }\n    const hc = Math.max(0.04, Rm * 0.1)\n    k.lathe(drv, [[cR * 1.12, yCan - 0.04], [cR * 1.12, yCan], [cR * 0.4, yCan + hc], [0, yCan + hc]], [0, 0, 0], 'y', 48)\n    k.lathe(drv, [[0, yCan - 0.005], [cR * 1.1, yCan - 0.04]], [0, 0, 0], 'y', 48)\n    top = yCan + hc\n  }\n  // terminal box (+Z), anti-condensation heater / RTD box (-Z), cables to the deck\n  const tb = clamp(Rm * 0.75, 0.14, 0.5)\n  const yT = yF + Hm * 0.45\n  const zT = Rm + finH + tb * 0.28\n  k.box(drv, [tb * 0.5, tb * 0.5, finH + 0.03], [0, yT, Rm + finH * 0.5])\n  k.rbox(drv, [tb * 1.1, tb * 0.9, tb * 0.55], tb * 0.06, [0, yT, zT])\n  k.rbox(drv, [tb * 1.0, tb * 0.8, tb * 0.06], tb * 0.03, [0, yT, zT + tb * 0.3])\n  k.around(6, tb * 0.42, [0, yT, zT + tb * 0.335], 'z', (p) => k.hex('nut', 0.016, 0.01, p, 'z'), Math.PI / 6)\n  for (const dx of [-tb * 0.25, tb * 0.25]) {\n    k.cyl('brass', tb * 0.08, tb * 0.14, [dx, yT - tb * 0.5, zT], 'y', 12)\n    k.spline('cable', [[dx, yT - tb * 0.56, zT], [dx, yT - tb * 1.4, zT + 0.04], [dx * 1.4, (yT + deck) / 2, zT + 0.1], [dx * 1.6, deck + 0.03, zT + 0.12]], tb * 0.065, 8, 30)\n  }\n  const ta = tb * 0.5\n  const zA = -(Rm + finH + ta * 0.22)\n  k.rbox(drv, [ta, ta * 0.8, ta * 0.45], ta * 0.08, [0, yT + tb * 0.2, zA])\n  k.cyl('brass', ta * 0.1, ta * 0.18, [0, yT + tb * 0.2 - ta * 0.45, zA], 'y', 10)\n  k.spline('cable', [[0, yT + tb * 0.2 - ta * 0.5, zA], [0, yT - 0.4, zA - 0.08], [0.1, deck + 0.03, zA - 0.1]], 0.012, 6, 24)\n  // lifting lugs, nameplate, earth boss, bearing vibration probes\n  for (const s of [-1, 1]) {\n    const x = s * (Rm + finH + 0.03)\n    k.box(drv, [0.08, 0.16, 0.035], [x, y1f - 0.06, 0])\n    k.torus('nut', 0.035, 0.009, [x + s * 0.02, y1f + 0.03, 0], 'z', 6, 14)\n  }\n  const pn = new THREE.Vector3(0.707, 0, 0.707)\n  const pr = Rm + finH + 0.006\n  const pw = Math.max(0.1, Rm * 0.5)\n  k.box(drv, [pw * 0.6, pw * 0.4, finH + 0.01], [pn.x * (Rm + finH * 0.5), yF + Hm * 0.68, pn.z * (Rm + finH * 0.5)], faceQ([pn.x, 0, pn.z]))\n  if (o.plate) tagPlate(k, o.plate, [pn.x * pr, yF + Hm * 0.68, pn.z * pr], [pn.x, 0, pn.z], pw)\n  else k.box('nameplate', [pw, pw * 0.6, 0.003], [pn.x * pr, yF + Hm * 0.68, pn.z * pr], faceQ([pn.x, 0, pn.z]))\n  k.cyl('brass', 0.012, 0.03, [-(Rm + finH * 0.5) * 0.7, yF + 0.2, (Rm + finH * 0.5) * 0.7], [-0.7, 0, 0.7], 8)\n  for (const a of [Math.PI / 2 + 0.6, Math.PI / 2 - 0.6]) {\n    const u: V3 = [Math.cos(a), 0, Math.sin(a)]\n    k.rod('stainless', [u[0] * (Rm * 0.75), yF + 0.05 + shL * 0.5, u[2] * (Rm * 0.75)], [u[0] * (Rm * 0.75 + 0.08), yF + 0.05 + shL * 0.5 + 0.04, u[2] * (Rm * 0.75 + 0.08)], 0.012, 8)\n  }\n  return top\n}\n\n/** Drain-rim baseplate (API 610): channels, sloped deck, rim, grout holes, anchor bolts, lifting lugs, drain. Top at y = hb. */\nexport function drainRimBaseplate(k: Kit, L: number, W: number, hb: number, mat: MatKey = 'equipmentPaintDark', x0 = 0) {\n  const t = 0.012\n  k.at([x0, 0, 0], undefined, () => {\n    k.box(mat, [L, 0.016, W + 0.08], [0, 0.008, 0])\n    for (const s of [-1, 1]) {\n      k.box(mat, [L, hb - 0.016, t], [0, hb / 2 + 0.008, s * (W / 2 - t / 2)])\n      k.box(mat, [t, hb - 0.016, W], [s * (L / 2 - t / 2), hb / 2 + 0.008, 0])\n      const n = Math.round(L / 0.45)\n      for (let i = 1; i < n; i++) k.box(mat, [0.01, hb - 0.03, 0.04], [-L / 2 + (i * L) / n, hb / 2 + 0.008, s * (W / 2 + 0.02)])\n    }\n    k.box(mat, [L - 2 * t, 0.01, W - 2 * t], [0, hb - 0.03, 0])\n    for (const s of [-1, 1]) {\n      k.box(mat, [L, 0.045, 0.02], [0, hb - 0.005, s * (W / 2 - 0.01)])\n      k.box(mat, [0.02, 0.045, W], [s * (L / 2 - 0.01), hb - 0.005, 0])\n    }\n    for (const s of [-1, 1]) k.box(mat, [L - 0.1, hb - 0.05, 0.12], [0, (hb - 0.05) / 2 + 0.016, s * W * 0.28])\n    const nG = Math.max(2, Math.round(L / 0.9))\n    for (let i = 0; i < nG; i++) {\n      const gx = -L / 2 + (L * (i + 0.5)) / nG\n      k.torus(mat, 0.05, 0.008, [gx, hb - 0.022, 0], 'y', 6, 24)\n      k.disc('rubberBlack', 0.048, [gx, hb - 0.0245, 0], 'y', 24)\n    }\n    const nA = Math.max(3, Math.round(L / 0.7) + 1)\n    for (let i = 0; i < nA; i++) {\n      const ax = -L / 2 + 0.08 + ((L - 0.16) * i) / (nA - 1)\n      for (const s of [-1, 1]) anchorBolt(k, [ax, 0.016, s * (W / 2 + 0.035)])\n    }\n    for (const sx of [-1, 1])\n      for (const sz of [-1, 1]) {\n        k.box(mat, [0.1, 0.1, 0.02], [sx * (L / 2 - 0.12), hb * 0.55, sz * (W / 2 + 0.01)])\n        k.torus('nut', 0.025, 0.006, [sx * (L / 2 - 0.12), hb * 0.55 + 0.02, sz * (W / 2 + 0.022)], 'z', 6, 14)\n      }\n    k.cyl(mat, 0.022, 0.12, [L / 2 + 0.05, hb - 0.04, -W / 2 + 0.15], 'x', 12)\n    k.hex('nut', 0.05, 0.03, [L / 2 + 0.11, hb - 0.04, -W / 2 + 0.15], 'x')\n    k.cyl('brass', 0.015, 0.03, [-L / 2 + 0.3, hb * 0.5, W / 2 + 0.015], 'z', 10)\n  })\n}\n\n/** Anchor bolt with washer and nut on a plate whose top is at p[1]. */\nexport function anchorBolt(k: Kit, p: V3, d = 0.022) {\n  k.cyl('stainless', d * 0.9, 0.004, [p[0], p[1] + 0.002, p[2]], 'y', 16)\n  k.hex('nut', d * 1.9, d * 1.1, [p[0], p[1] + 0.004 + d * 0.55, p[2]])\n  k.cyl('boltXylan', d / 2, d * 3, [p[0], p[1] + d * 1.5, p[2]], 'y', 8)\n}\n\n/** Machined mounting pad with jack-screw block, top at y. */\nexport function mountPad(k: Kit, x: number, y: number, z: number, sx = 0.22, sz = 0.2, jack = 1, mat: MatKey = 'equipmentPaintDark') {\n  k.box('flangeSteel', [sx, 0.025, sz], [x, y - 0.0125, z])\n  if (!jack) return\n  k.box(mat, [0.04, 0.06, 0.06], [x + jack * (sx / 2 + 0.05), y + 0.02, z])\n  k.cyl('boltXylan', 0.008, 0.08, [x + jack * (sx / 2 + 0.03), y + 0.03, z], 'x', 6)\n}\n\n/** Local control station on a post (start / stop, E-stop, ammeter), facing +Z; base at `foot`. */\nexport function controlStation(k: Kit, foot: V3, face: V3 = [0, 0, 1], h = 1.2) {\n  const [lx, y0, lz] = foot\n  const q = faceQ(face)\n  const n = new THREE.Vector3(...face).normalize()\n  const P = (u: number, v: number, w: number): V3 => new THREE.Vector3(u, v, w).applyQuaternion(q).add(new THREE.Vector3(lx, 0, lz)).toArray() as V3\n  k.rod('structSteel', [lx, y0, lz], [lx, h + 0.25, lz], 0.03, 12)\n  k.box('structSteel', [0.15, 0.012, 0.15], [lx, y0 + 0.006, lz])\n  k.box('structSteel', [0.04, 0.3, 0.02], P(0, h, 0.04), q)\n  k.rbox('stainless', [0.24, 0.32, 0.13], 0.012, P(0, h, 0.115), q)\n  const tilt = q.clone().multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), 0.15))\n  k.box('stainless', [0.3, 0.012, 0.2], P(0, h + 0.18, 0.12), tilt)\n  const f = 0.18\n  k.cyl('safetyGreen', 0.016, 0.02, P(-0.055, h + 0.05, f), n.toArray() as V3, 16)\n  k.cyl('actuatorRed', 0.016, 0.02, P(0.055, h + 0.05, f), n.toArray() as V3, 16)\n  k.cyl('actuatorYellow', 0.04, 0.004, P(0, h - 0.07, f - 0.002), n.toArray() as V3, 24)\n  k.cyl('actuatorRed', 0.012, 0.03, P(0, h - 0.07, f + 0.012), n.toArray() as V3, 12)\n  k.dome('actuatorRed', 0.028, P(0, h - 0.07, f + 0.025), n.toArray() as V3, Math.PI / 2, 0.6)\n  gauge(k, P(0, h + 0.11, f - 0.01), n.toArray() as V3, 0.06, 'bar', 600)\n  k.hex('brass', 0.03, 0.02, P(0, h - 0.17, 0.11))\n  k.spline('cable', [P(0, h - 0.18, 0.11), P(0, h - 0.35, 0.06), P(0, y0 + 0.2, 0.04), P(0.1, y0 + 0.02, 0)], 0.008, 6, 24)\n}\n\n/** Pressure gauge on a root valve + siphon above a pipe top at `p` (dial facing +normal). */\nexport function pipeGauge(k: Kit, p: V3, normal: V3 = [0, 0, 1], max = 25, d = 0.1) {\n  const [x, y, z] = p\n  k.cyl('stainless', 0.012, 0.1, [x, y + 0.05, z], 'y', 8)\n  k.hex('stainless', 0.04, 0.035, [x, y + 0.09, z])\n  k.torus('stainless', 0.025, 0.006, [x, y + 0.14, z], [normal[0], 0, normal[2]], 6, 18)\n  k.cyl('stainless', 0.01, 0.05, [x, y + 0.19, z], 'y', 8)\n  gauge(k, [x + normal[0] * 0.02, y + 0.25, z + normal[2] * 0.02], normal, d, 'bar', max)\n}\n\n/** Spiral volute outline (Archimedean, r0 at the tongue to r1) with a straight tangential outlet of length `out` along +Y at u ∈ [r0, r1]. */\nexport function voluteShape(r0: number, r1: number, out: number, n = 48) {\n  const s = new THREE.Shape()\n  s.moveTo(r0, 0)\n  for (let i = 1; i <= n; i++) {\n    const th = (i / n) * TAU\n    const r = r0 + ((r1 - r0) * i) / n\n    s.lineTo(Math.cos(th) * r, Math.sin(th) * r)\n  }\n  s.lineTo(r1, out)\n  s.lineTo(r0, out)\n  s.closePath()\n  return s\n}\n\n/** Extruded shape with rounded (bevelled) edges, depth along local +Z centred on z = 0, placed by m. */\nexport function castExtrude(k: Kit, mat: MatKey, shape: THREE.Shape, depth: number, bevel: number, m: THREE.Matrix4, seg = 12) {\n  const g = new THREE.ExtrudeGeometry(shape, { depth: Math.max(depth - 2 * bevel, 0.001), bevelEnabled: true, bevelThickness: bevel, bevelSize: bevel, bevelSegments: 3, curveSegments: seg })\n  g.translate(0, 0, -(depth - 2 * bevel) / 2)\n  k.add(mat, g, m)\n}\n\n/** Top-mounted air-to-air cooler (IC611) on a horizontal HV motor whose DE face is at xm, shaft height ys. */\nexport function topCooler(k: Kit, xm: number, ys: number, Rm: number, Lm: number, mat: MatKey = 'motorPaint') {\n  const ch = Rm * 0.7\n  const y = ys + Rm * 1.12 + ch / 2\n  k.box(mat, [Lm * 0.3, Rm * 0.25, Rm * 0.9], [xm - Lm * 0.5, ys + Rm * 1.05, 0])\n  k.rbox(mat, [Lm * 0.75, ch, Rm * 1.7], 0.03, [xm - Lm * 0.5, y, 0])\n  for (let i = 0; i < 6; i++) k.box('rubberBlack', [Lm * 0.7, 0.01, Rm * 1.71], [xm - Lm * 0.5, y - ch * 0.35 + i * ch * 0.14, 0])\n  k.cyl(mat, ch * 0.45, 0.1, [xm - Lm * 0.875 - 0.05, y, 0], 'x', 28)\n  k.disc(fanGuardMaterial(), ch * 0.42, [xm - Lm * 0.875 - 0.101, y, 0], '-x', 24)\n}\n\n/** Constant-level oiler: arm from the bearing-housing surface point p along n, glass reservoir above. */\nexport function oiler(k: Kit, p: V3, n: V3 = [0, 0, 1], s = 1) {\n  const o = new THREE.Vector3(...p).addScaledVector(new THREE.Vector3(...n).normalize(), 0.07 * s)\n  k.rod('stainless', p, o.toArray() as V3, 0.008 * s, 8)\n  k.cyl('stainless', 0.03 * s, 0.03 * s, [o.x, o.y + 0.015 * s, o.z], 'y', 16)\n  k.cyl('glass', 0.032 * s, 0.09 * s, [o.x, o.y + 0.075 * s, o.z], 'y', 16)\n  k.dome('glass', 0.032 * s, [o.x, o.y + 0.12 * s, o.z], 'y', Math.PI / 2, 1)\n}\n\n/** Small valve (forged body, bonnet, handwheel) on a vertical line at p, handwheel up. */\nexport function smallValve(k: Kit, p: V3, s = 1, dir: Orient = 'y') {\n  k.at(p, dir, () => {\n    k.box('valveBody', [0.06 * s, 0.07 * s, 0.05 * s], [0, 0, 0])\n    k.cyl('valveBody', 0.016 * s, 0.07 * s, [0, 0.07 * s, 0], 'y', 10)\n    k.handwheel('handwheel', 0.07 * s, [0, 0.11 * s, 0], 'y')\n  })\n}\n\n/** API 682 Plan 23 seal cooler on a post: shell with bolted heads and cooling-water nozzles. Returns the top and bottom process connection points. */\nexport function sealCooler(k: Kit, foot: V3, yTop: number, r = 0.055) {\n  const [x, y0, z] = foot\n  const h = Math.max(0.3, r * 6)\n  const yc = yTop - h / 2\n  k.rod('structSteel', [x, y0, z], [x, yc - h / 2 - 0.02, z], 0.022, 10)\n  k.box('structSteel', [0.12, 0.012, 0.12], [x, y0 + 0.006, z])\n  k.box('structSteel', [0.03, 0.12, 0.008], [x, yc - h / 2 + 0.02, z - r - 0.004])\n  k.cyl('stainless', r, h, [x, yc, z], 'y', 20)\n  for (const s of [-1, 1]) {\n    k.cyl('stainless', r * 1.3, 0.02, [x, yc + s * (h / 2 + 0.01), z], 'y', 20)\n    k.boltCircle('boltXylan', 'nut', 6, r * 1.15, [x, yc + s * (h / 2 + 0.01) - 0.015, z], 'y', 0.006, 0.03, [0.005, 0.025])\n  }\n  for (const s of [-1, 1]) {\n    k.rod('stainless', [x, yc + s * h * 0.35, z], [x + r + 0.05, yc + s * h * 0.35, z], 0.01, 8)\n    k.cyl('flangeSteel', 0.018, 0.012, [x + r + 0.05, yc + s * h * 0.35, z], 'x', 12)\n  }\n  return { top: [x, yc + h / 2 + 0.02, z] as V3, bottom: [x, yc - h / 2 - 0.02, z] as V3 }\n}\n"
    }
  ],
  "categories": [
    "rotating"
  ],
  "meta": {
    "export": "VerticalInlinePump",
    "anchor": "base",
    "standards": [
      "API 610 (OH3)",
      "API 682 Plan 11",
      "IEC 60034 / 60072",
      "ASME B16.5"
    ]
  }
}