{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dp-transmitter",
  "type": "registry:component",
  "title": "Differential pressure transmitter",
  "description": "DP transmitter with coplanar flange (H / L marked) and Ex d head on a 5-valve manifold: two block, two equalise, two vent valves. Pipe-stand mounted with an optional stainless sunshade; separate HP and LP 1/2\" impulse lines with compression fittings and tube clamps fall at 1:10 to two 1/2\" flanged process connections (gas service, transmitter above the taps).",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "instruments/dp-transmitter.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/instruments/dp-transmitter.tsx",
      "content": "// Differential pressure transmitter: coplanar sensor and Ex d head with LCD on a 5-valve manifold, 2\" pipe stand, HP / LP impulse tubing to flanged process connections. IEC 60079-1, ASME B16.5.\nimport type { MatKey } from '../lib/kit'\nimport { KitMesh, useKit, type GroupProps } from '../lib/mesh'\nimport { port, type Port } from '../lib/ports'\nimport type { Rating } from '../lib/standards'\nimport { buildStand, standLayout } from './_stand'\n\nexport interface DpTransmitterProps {\n  /** LCD centreline height above deck, m */\n  height?: number\n  /** class of the 1/2\" process flanges */\n  rating?: Rating\n  housing?: MatKey\n  /** stainless sunshade over the head */\n  sunshade?: boolean\n  tag?: string\n}\n\nexport const defaults = { height: 1.35, rating: 600 as Rating, housing: 'actuatorBlue' as MatKey, tag: 'PDT-1001', sunshade: true }\n\nexport function ports(p: DpTransmitterProps = {}): Port[] {\n  const { height, rating } = { ...defaults, ...p }\n  const { faces } = standLayout({ valves: 5, height })\n  return [\n    port('hp', 'High-pressure side', faces[0], [-1, 0, 0], 0.5, rating, 'instrument'),\n    port('lp', 'Low-pressure side', faces[1], [-1, 0, 0], 0.5, rating, 'instrument'),\n  ]\n}\n\nexport function DpTransmitter({ height = defaults.height, rating = defaults.rating, housing = defaults.housing, tag = defaults.tag, sunshade = defaults.sunshade, ...group }: DpTransmitterProps & GroupProps) {\n  const data = useKit((k) => buildStand(k, { valves: 5, height, rating, housing, tag, sunshade }), [height, rating, housing, tag, sunshade])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n    </group>\n  )\n}\n\nexport default DpTransmitter\n"
    },
    {
      "path": "instruments/_stand.ts",
      "type": "registry:file",
      "target": "components/deck-twin/instruments/_stand.ts",
      "content": "// Stand-mounted transmitter layout shared by the gauge- and DP-transmitter: 2\" stand, offset bracket, manifold, head, sunshade, sloped impulse tubing to 1/2\" flanges.\nimport { Kit, eul, type MatKey, type V3 } from '../lib/kit'\nimport type { Rating } from '../lib/standards'\nimport { SENSOR_H, SS, cable, coplanarSensor, manifold, pipeStand, standBracket, tag, tubeClamp, tubeFitting, tubeFlange, tubing } from './_parts'\n\nexport interface StandOpts {\n  valves: 2 | 5\n  /** LCD centreline height above deck, m */\n  height: number\n  rating: Rating\n  housing: MatKey\n  tag: string\n  sunshade: boolean\n}\n\nconst RS = 0.03016 // 2\" OD / 2\nconst PLATE_T = 0.006\nconst FLANGE_X = -0.24\nconst SPACER = 0.06 // offset so the head's rear cover clears the bracket and sunshade back plate\nconst SLOPE = 0.1 // impulse-line fall towards the process, 1:10 (gas service, transmitter above the taps)\n\n/** Positions shared by geometry and ports. */\nexport function standLayout(o: Pick<StandOpts, 'valves' | 'height'>) {\n  const mh = o.valves === 2 ? 0.075 : 0.06\n  const yt = o.height - SENSOR_H\n  const yb = yt - mh\n  const zc = RS + PLATE_T + SPACER + 0.025\n  const xs = o.valves === 2 ? [0] : [-0.027, 0.027]\n  const y0 = Math.max(0.32, yb - 0.42)\n  const faces: V3[] = xs.map((_, i) => [FLANGE_X, y0 - i * 0.15, zc])\n  return { yt, yb, zc, xs, faces }\n}\n\nexport function buildStand(k: Kit, o: StandOpts) {\n  const { yt, yb, zc, faces } = standLayout(o)\n  const rs = pipeStand(k, yt - 0.012)\n  const zp = standBracket(k, rs, yb - 0.13, yt - 0.012, -0.05, 0.15, [yb - 0.035, yb - 0.105])\n  k.box('galvanised', [0.05, 0.05, SPACER], [0, yb + 0.03, zp + SPACER / 2])\n  for (const dx of [-0.016, 0.016]) k.hex('nut', 0.014, 0.006, [dx, yb + 0.03, zp + SPACER + 0.003], 'z')\n  const m = manifold(k, [0, yb, zc], o.valves)\n  const head = coplanarSensor(k, [0, m.top, zc], o.housing, o.valves === 5)\n  m.inlets.forEach((inl, i) => {\n    const t0 = tubeFitting(k, inl, [0, -1, 0])\n    const face = faces[i]\n    const entry = tubeFlange(k, 0.5, o.rating, face, '-x')\n    const bendY = face[1] + SLOPE * (inl[0] - entry[0])\n    tubing(k, [t0, [inl[0], bendY, zc], entry])\n    tubeClamp(k, rs, inl[0], yb - 0.2, zc)\n    if (face[1] < yb - 0.45) tubeClamp(k, rs, inl[0], (face[1] + yb - 0.2) / 2, zc)\n  })\n  tag(k, o.tag, [0.1, yb - 0.07, zp + 0.0009], 'z', 0.07)\n  if (o.sunshade) sunshade(k, rs, yt - 0.012, o.height, zc)\n  const g: V3 = [head[0] + 0.106, head[1], head[2] + 0.02]\n  cable(k, [g, [0.16, g[1] - 0.06, g[2]], [0.2, o.height - 0.25, zc - 0.03], [0.2, yb - 0.2, 0], [0.06, yb - 0.32, -rs - 0.01], [0, 0.35, -rs - 0.008], [0, 0.07, -rs - 0.04], [0, 0.023, -0.12]])\n}\n\n/** Stainless sunshade: back plate bolted above the bracket, sloped roof with a drip lip, folded side cheeks. */\nfunction sunshade(k: Kit, rs: number, y0: number, H: number, zc: number) {\n  const x0 = -0.14\n  const x1 = 0.18\n  const yTop = H + 0.1\n  const z0 = rs\n  const z1 = zc + 0.15\n  const t = 0.003\n  k.box(SS, [x1 - x0, yTop - y0, t], [(x0 + x1) / 2, (y0 + yTop) / 2, z0 + 0.006 + t / 2])\n  for (const dx of [-0.03, 0.03]) k.hex('nut', 0.014, 0.006, [dx, y0 + 0.02, z0 + 0.006 + t + 0.003], 'z')\n  const fall = 0.03\n  const len = Math.hypot(z1 - z0, fall)\n  const a = Math.atan2(fall, z1 - z0)\n  k.box(SS, [x1 - x0, t, len], [(x0 + x1) / 2, yTop - fall / 2, (z0 + z1) / 2], eul(a, 0, 0))\n  k.box(SS, [x1 - x0, 0.025, t], [(x0 + x1) / 2, yTop - fall - 0.0125, z1])\n  for (const x of [x0, x1]) k.box(SS, [t, 0.12, z1 - z0], [x, yTop - fall / 2 - 0.06, (z0 + z1) / 2])\n}\n"
    },
    {
      "path": "instruments/_parts.ts",
      "type": "registry:file",
      "target": "components/deck-twin/instruments/_parts.ts",
      "content": "// Shared instrument parts: Ex d transmitter heads, coplanar sensors, valve manifolds, tube fittings, pipe stands, flanged thermowells and level chambers.\nimport * as THREE from 'three'\nimport { Kit, axisVec, type Axis, type MatKey, type V3 } from '../lib/kit'\nimport { tagPlate } from '../lib/parts'\nimport { flange as flangeDims, pipeOD, pipeWall, type Rating, type Schedule } from '../lib/standards'\nimport { lcdMaterial } from '../lib/textures'\n\nexport const SS: MatKey = 'stainless'\n/** 1/2\" OD instrument tubing, radius m */\nexport const TUBE_R = 0.00635\n/** height from the coplanar flange underside to the head centreline, m */\nexport const SENSOR_H = 0.182\n\nconst V = (v: V3) => new THREE.Vector3(...v)\nconst A = (v: THREE.Vector3) => v.toArray() as V3\nexport const addV = (a: V3, d: V3, s = 1): V3 => [a[0] + d[0] * s, a[1] + d[1] * s, a[2] + d[2] * s]\n\n/** Radial segments for a round part of diameter d, m (smooth silhouettes on big flanges and bodies). */\nexport const segFor = (d: number) => (d > 0.5 ? 72 : d > 0.25 ? 56 : d > 0.1 ? 44 : 32)\n\n/** B16.5 weld-neck flange, raised face at `face` pointing along +axis, with an open bore. Returns face-to-weld length. */\nexport function flange(k: Kit, nps: number, rating: Rating, face: V3, axis: Axis, studs = false) {\n  const f = flangeDims(nps, rating)\n  const ro = pipeOD(nps) / 2\n  const ri = ro - pipeWall(nps, '80')\n  const hubR = Math.max(f.hubDia / 2, ro * 1.05)\n  const rf = f.raisedFaceHeight\n  const L = f.hubLength + rf\n  const seg = segFor(f.od)\n  const v = axisVec(axis)\n  const base = A(V(face).addScaledVector(v, -L))\n  const back = L - rf - f.thickness\n  const o = A(v)\n  k.lathe('flangeSteel', [[ro * 0.999, 0], [hubR, Math.max(back - 0.004, 0.001)], [hubR + 0.004, back], [f.od / 2, back], [f.od / 2, L - rf], [f.raisedFace / 2, L - rf]], base, o, seg)\n  k.lathe('machined', [[f.raisedFace / 2, L - rf], [f.raisedFace / 2, L], [ri, L]], base, o, seg)\n  k.lathe('pipeBore', [[ri, L], [ri, 0]], base, o, segFor(ri * 2))\n  if (studs) {\n    const st = A(V(face).addScaledVector(v, -rf - f.thickness - f.boltDia))\n    k.boltCircle('boltXylan', 'nut', f.bolts, f.boltCircle / 2, st, axis, f.boltDia, f.thickness + f.boltDia * 2.4, [f.boltDia * 0.45])\n  }\n  return L\n}\n\n/** Blind flange with gasket and double-nutted studs closing a face that points along +axis. */\nexport function blindFlange(k: Kit, nps: number, rating: Rating, face: V3, axis: Axis) {\n  const f = flangeDims(nps, rating)\n  const v = axisVec(axis)\n  const t = f.thickness * 1.15\n  k.cyl('flangeSteel', f.od / 2, t, A(V(face).addScaledVector(v, 0.0045 + t / 2)), A(v), segFor(f.od))\n  k.cyl('gasket', f.raisedFace / 2 + 0.006, 0.0045, A(V(face).addScaledVector(v, 0.00225)), A(v), segFor(f.raisedFace))\n  const studL = f.thickness * 2.2 + t + f.boltDia * 2\n  const st = A(V(face).addScaledVector(v, -f.thickness - f.raisedFaceHeight - f.boltDia))\n  k.boltCircle('boltXylan', 'nut', f.bolts, f.boltCircle / 2, st, axis, f.boltDia, studL, [f.boltDia * 0.45, studL - f.boltDia * 0.45])\n}\n\n/** Hollow pipe between two points with diameter-scaled segments. */\nexport function pipe(k: Kit, a: V3, b: V3, nps: number, mat: MatKey = 'pipePaint', schedule: Schedule = 'STD') {\n  const od = pipeOD(nps)\n  k.pipe(mat, a, b, od, pipeWall(nps, schedule), segFor(od))\n}\n\n/** Axis perpendicular to `dir`, horizontal where possible (for T-bar handles). */\nfunction crossAxis(dir: V3): V3 {\n  const d = V(dir).normalize()\n  if (Math.abs(d.y) > 0.9) return [1, 0, 0]\n  return A(new THREE.Vector3(0, 1, 0).cross(d).normalize())\n}\n\n/**\n * Dual-compartment Ex d housing, body axis along Z: LCD cover on +Z, terminal cover on -Z, conduit entries on ±X\n * (cable gland on +X, stopping plug on -X), neck boss underneath. Centre at `c`. Returns the gland exit point.\n */\nexport function exHead(k: Kit, c: V3, mat: MatKey, opts: { lcd?: boolean; r?: number } = {}): V3 {\n  const R = opts.r ?? 0.048\n  const s = R / 0.048\n  const L = 0.1 * s\n  const [x, y, z] = c\n  k.cyl(mat, R, L, c, 'z', 32)\n  k.cyl(mat, 0.026 * s, 0.03 * s, [x, y - R + 0.004 * s, z], 'y', 20)\n  for (const d of [-1, 1]) {\n    k.cyl(mat, R * 1.1, 0.024 * s, [x, y, z + d * (L / 2 + 0.012 * s)], 'z', 32)\n    k.torus(mat, R * 1.1, 0.0028 * s, [x, y, z + d * (L / 2 + 0.006 * s)], 'z', 6, 32)\n    k.hex('nut', 0.008 * s, 0.006 * s, [x + R * 0.75, y - R * 0.75, z + d * (L / 2 + 0.003 * s)], 'z')\n  }\n  const zf = z + L / 2 + 0.024 * s\n  if (opts.lcd ?? true) {\n    k.torus(mat, R * 0.86, 0.004 * s, [x, y, zf], 'z', 6, 32)\n    k.disc(lcdMaterial(), R * 0.76, [x, y, zf + 0.0006], 'z', 28)\n    k.disc('glass', R * 0.84, [x, y, zf + 0.0022], 'z', 28)\n  }\n  for (const d of [-1, 1]) k.cyl(mat, 0.014 * s, 0.024 * s, [x + d * (R + 0.006 * s), y, z + 0.02 * s], 'x', 14)\n  k.hex('brass', 0.027 * s, 0.018 * s, [x + R + 0.026 * s, y, z + 0.02 * s], 'x')\n  k.cyl('brass', 0.011 * s, 0.014 * s, [x + R + 0.042 * s, y, z + 0.02 * s], 'x', 12)\n  k.cyl('rubberBlack', 0.0078 * s, 0.01 * s, [x + R + 0.053 * s, y, z + 0.02 * s], 'x', 10)\n  k.hex(SS, 0.025 * s, 0.01 * s, [x - R - 0.023 * s, y, z + 0.02 * s], 'x')\n  k.box('nameplate', [0.052 * s, 0.002, 0.03 * s], [x, y + R + 0.0008, z])\n  k.cyl(SS, 0.005 * s, 0.008 * s, [x, y - R * 0.6, z - L / 2 - 0.004 * s], 'z', 8)\n  return [x + R + 0.058 * s, y, z + 0.02 * s]\n}\n\n/** Coplanar sensor module on a flange whose underside is at `b`. dp adds the H / L marks. Returns the head centre. */\nexport function coplanarSensor(k: Kit, b: V3, mat: MatKey, dp = false): V3 {\n  const [x, y, z] = b\n  k.box(SS, [0.086, 0.024, 0.056], [x, y + 0.012, z])\n  for (const dx of [-0.032, 0.032]) for (const dz of [-0.02, 0.02]) k.hex(SS, 0.012, 0.007, [x + dx, y + 0.0275, z + dz])\n  for (const s of [-1, 1]) {\n    k.cyl(SS, 0.0055, 0.012, [x + s * 0.049, y + 0.012, z], 'x', 8)\n    k.hex(SS, 0.011, 0.007, [x + s * 0.058, y + 0.012, z], 'x')\n  }\n  if (dp) for (const s of [-1, 1]) k.box('nameplate', [0.012, 0.01, 0.0015], [x + s * 0.03, y + 0.012, z + 0.0285])\n  k.cyl(SS, 0.043, 0.008, [x, y + 0.028, z], 'y', 28)\n  k.cyl(SS, 0.04, 0.07, [x, y + 0.067, z], 'y', 28)\n  k.torus(SS, 0.04, 0.002, [x, y + 0.08, z], 'y', 4, 28)\n  k.cyl(SS, 0.02, 0.034, [x, y + 0.117, z], 'y', 16)\n  return exHeadOn(k, [x, y + 0.134, z], mat)\n}\n\nfunction exHeadOn(k: Kit, neckTop: V3, mat: MatKey): V3 {\n  const c: V3 = [neckTop[0], neckTop[1] + 0.048, neckTop[2]]\n  exHead(k, c, mat)\n  return c\n}\n\n/** Small-bore valve operator out of a block face: bonnet, packing nut, stem and T-bar. */\nexport function valveHandle(k: Kit, pos: V3, dir: V3, bar: MatKey = 'handwheel', s = 1) {\n  const d = V(dir).normalize()\n  const dd = A(d)\n  k.hex(SS, 0.021 * s, 0.014 * s, addV(pos, dd, 0.007 * s), dd)\n  k.hex(SS, 0.016 * s, 0.01 * s, addV(pos, dd, 0.019 * s), dd)\n  k.cyl(SS, 0.0035 * s, 0.016 * s, addV(pos, dd, 0.031 * s), dd, 8)\n  k.cyl(bar, 0.0045 * s, 0.056 * s, addV(pos, dd, 0.04 * s), crossAxis(dd), 8)\n}\n\n/** Twin-ferrule compression fitting at a port facing `dir`; the tube leaves 0.026 m out. */\nexport function tubeFitting(k: Kit, pos: V3, dir: V3) {\n  const dd = A(V(dir).normalize())\n  k.hex(SS, 0.024, 0.012, addV(pos, dd, 0.006), dd)\n  k.cyl(SS, 0.008, 0.004, addV(pos, dd, 0.014), dd, 12)\n  k.hex(SS, 0.021, 0.01, addV(pos, dd, 0.021), dd)\n  return addV(pos, dd, 0.026)\n}\n\n/** Instrument tubing along a polyline with tight bends. */\nexport function tubing(k: Kit, pts: V3[]) {\n  k.tube('tubing', pts, TUBE_R, 10, 0.038)\n}\n\nexport interface ManifoldGeom {\n  top: number\n  bottom: number\n  /** process inlets on the underside */\n  inlets: V3[]\n  w: number\n  d: number\n}\n\n/**\n * Bar-stock valve manifold, flat back on -Z. 2-valve: block + bleed. 3-valve: 2 block + equalise.\n * 5-valve: 2 block + 2 equalise (front) + 2 vents (ends). `b` is the bottom centre.\n */\nexport function manifold(k: Kit, b: V3, n: 2 | 3 | 5): ManifoldGeom {\n  const [x, y, z] = b\n  const w = n === 2 ? 0.06 : n === 3 ? 0.1 : 0.12\n  const h = n === 2 ? 0.075 : 0.06\n  const d = 0.05\n  k.box(SS, [w, h, d], [x, y + h / 2, z])\n  const front = z + d / 2\n  const yc = y + h / 2\n  if (n === 2) {\n    valveHandle(k, [x, y + h * 0.62, front], [0, 0, 1], 'handwheel')\n    valveHandle(k, [x - w / 2, y + h * 0.3, z], [-1, 0, 0], 'actuatorRed', 0.75)\n    k.hex('brass', 0.014, 0.01, [x + w / 2 + 0.005, y + h * 0.3, z], 'x')\n  } else {\n    for (const s of [-1, 1]) valveHandle(k, [x + s * w * 0.32, y + h * 0.33, front], [0, 0, 1], 'handwheel')\n    valveHandle(k, [x, y + h * 0.72, front], [0, 0, 1], 'actuatorRed', 0.8)\n    if (n === 5) for (const s of [-1, 1]) valveHandle(k, [x + s * w / 2, yc, z], [s, 0, 0], 'actuatorRed', 0.7)\n    else for (const s of [-1, 1]) k.hex('brass', 0.014, 0.01, [x + s * (w / 2 + 0.005), yc, z], 'x')\n  }\n  const inlets: V3[] = n === 2 ? [[x, y, z]] : [[x - 0.027, y, z], [x + 0.027, y, z]]\n  for (const p of inlets) k.cyl(SS, 0.011, 0.004, addV(p, [0, -1, 0], 0.002), 'y', 12)\n  return { top: y + h, bottom: y, inlets, w, d }\n}\n\n/** 2\" pipe stand (galvanised, Sch 80) on a gusseted base plate with four anchor bolts. Top capped at `top`. */\nexport function pipeStand(k: Kit, top: number) {\n  const od = pipeOD(2)\n  const tp = 0.016\n  const P = 0.26\n  k.box('galvanised', [P, tp, P], [0, tp / 2, 0])\n  k.pipe('galvanised', [0, tp, 0], [0, top, 0], od, pipeWall(2, '80'), 24)\n  k.cyl('galvanised', od / 2 + 0.002, 0.006, [0, top + 0.003, 0], 'y', 24)\n  const g = new THREE.Shape()\n  g.moveTo(0, 0)\n  g.lineTo(0.08, 0)\n  g.lineTo(0.08, 0.012)\n  g.lineTo(0, 0.1)\n  g.closePath()\n  for (let i = 0; i < 4; i++) {\n    const a = (i * Math.PI) / 2\n    const m = new THREE.Matrix4().makeRotationY(a).multiply(new THREE.Matrix4().makeTranslation(od / 2 - 0.001, tp, -0.004))\n    k.extrude('galvanised', g, 0.008, m)\n  }\n  for (const sx of [-1, 1])\n    for (const sz of [-1, 1]) {\n      const p: V3 = [sx * (P / 2 - 0.035), tp, sz * (P / 2 - 0.035)]\n      k.cyl('galvanised', 0.016, 0.003, addV(p, [0, 1, 0], 0.0015), 'y', 16)\n      k.hex('nut', 0.028, 0.014, addV(p, [0, 1, 0], 0.01), 'y')\n      k.cyl('galvanised', 0.008, 0.035, addV(p, [0, 1, 0], 0.012), 'y', 8)\n    }\n  k.cyl('galvanised', 0.008, 0.012, [od / 2 + 0.005, 0.22, 0], 'x', 8)\n  return od / 2\n}\n\n/**\n * Bracket plate on the stand front (+Z) from y0 to y1, x0 to x1, held by two U-bolts at `uy`. Returns the plate front z.\n */\nexport function standBracket(k: Kit, rs: number, y0: number, y1: number, x0: number, x1: number, uy: number[]) {\n  const t = 0.006\n  k.box('galvanised', [x1 - x0, y1 - y0, t], [(x0 + x1) / 2, (y0 + y1) / 2, rs + t / 2])\n  const R = rs + 0.006\n  for (const y of uy) {\n    k.torus('galvanised', R, 0.005, [0, y, 0], 'y', 6, 20, Math.PI)\n    for (const s of [-1, 1]) {\n      k.rod('galvanised', [s * R, y, 0], [s * R, y, rs + t + 0.016], 0.005, 8)\n      k.hex('nut', 0.016, 0.008, [s * R, y, rs + t + 0.004], 'z')\n      k.cyl('galvanised', 0.012, 0.002, [s * R, y, rs + t + 0.001], 'z', 12)\n    }\n  }\n  return rs + t\n}\n\n/** Tube clamp holding a vertical tube at (x, y, z) off the stand front. */\nexport function tubeClamp(k: Kit, rs: number, x: number, y: number, z: number) {\n  k.box('galvanised', [0.028, 0.022, z - rs], [x, y, (z + rs) / 2])\n  k.torus('galvanised', TUBE_R + 0.003, 0.0025, [x, y, z], 'y', 6, 16)\n}\n\n/** Tubing-to-flange adapter: weld-neck flange with its face at `face` pointing along `axis`, tube entering the hub. Returns the tube entry point. */\nexport function tubeFlange(k: Kit, nps: number, rating: Rating, face: V3, axis: Axis) {\n  const L = flange(k, nps, rating, face, axis)\n  const d = A(axisVec(axis))\n  const hub = addV(face, d, -L)\n  k.cyl(SS, pipeOD(nps) / 2, 0.014, addV(hub, d, -0.007), A(axisVec(axis).negate()), 16, TUBE_R * 1.4)\n  return addV(hub, d, -0.014)\n}\n\n/** Cable from a gland that drops and runs back to `end` (a tray or deck) as a smooth spline. */\nexport function cable(k: Kit, pts: V3[]) {\n  k.spline('cable', pts, 0.0065, 6, 48)\n}\n\n/** Wire-tied stamped tag plate facing +normal. */\nexport function tag(k: Kit, text: string, pos: V3, normal: Axis, w = 0.075) {\n  if (text) tagPlate(k, text, pos, normal, w)\n}\n\n/**\n * Flanged thermowell with its raised face at y = 0 facing -Y: solid forged flange, tapered stem below to `insertion`,\n * lagging extension, union and nipple above. Returns the top y.\n */\nexport function flangedThermowell(k: Kit, nps: number, rating: Rating, insertion: number, extension: number) {\n  const f = flangeDims(nps, rating)\n  const rf = f.raisedFaceHeight\n  const t = f.thickness\n  k.lathe('machined', [[0.004, 0], [f.raisedFace / 2, 0], [f.raisedFace / 2, rf]], [0, 0, 0], 'y', segFor(f.od))\n  k.lathe('flangeSteel', [[f.raisedFace / 2, rf], [f.od / 2, rf], [f.od / 2, rf + t], [0.02, rf + t]], [0, 0, 0], 'y', segFor(f.od))\n  k.around(f.bolts, f.boltCircle / 2, [0, rf + t + 0.0004, 0], 'y', (p) => k.cyl('pipeBore', f.boltDia * 0.56, 0.001, p, 'y', 10), Math.PI / f.bolts)\n  const r0 = Math.min(0.0135, pipeOD(nps) / 2 - 0.006)\n  k.cyl(SS, r0 + 0.003, 0.012, [0, -0.006, 0], 'y', 20)\n  k.cyl(SS, 0.0095, insertion - 0.012, [0, -0.012 - (insertion - 0.012) / 2, 0], 'y', 20, r0)\n  k.dome(SS, 0.0095, [0, -insertion, 0], '-y', Math.PI / 2, 1, 16)\n  let y = rf + t\n  k.cyl(SS, 0.02, 0.012, [0, y + 0.006, 0], 'y', 20)\n  k.hex(SS, 0.036, 0.018, [0, y + 0.021, 0])\n  y += 0.03\n  k.cyl(SS, 0.0107, extension, [0, y + extension / 2, 0], 'y', 14)\n  y += extension\n  k.hex(SS, 0.036, 0.016, [0, y + 0.008, 0])\n  k.cyl(SS, 0.017, 0.006, [0, y + 0.019, 0], 'y', 16)\n  k.hex(SS, 0.032, 0.016, [0, y + 0.03, 0])\n  y += 0.038\n  return y\n}\n\n/** Blind-flanged end of a vertical chamber, flange face at y facing `dir` (±1), with a 1/2\" valve and plug. */\nfunction chamberEnd(k: Kit, nps: number, rating: Rating, y: number, dir: 1 | -1, valve: boolean) {\n  const face: V3 = [0, y, 0]\n  flange(k, nps, rating, face, dir > 0 ? 'y' : '-y', true)\n  blindFlange(k, nps, rating, face, dir > 0 ? 'y' : '-y')\n  if (!valve) return\n  const f = flangeDims(nps, rating)\n  const yo = y + dir * (0.0045 + f.thickness * 1.15)\n  const off = 0\n  k.cyl(SS, 0.0107, 0.05, [off, yo + dir * 0.025, 0], 'y', 12)\n  k.box(SS, [0.034, 0.04, 0.034], [off, yo + dir * 0.07, 0])\n  valveHandle(k, [off, yo + dir * 0.07, 0.017], [0, 0, 1], 'actuatorRed', 0.9)\n  k.cyl(SS, 0.0107, 0.03, [off, yo + dir * 0.105, 0], 'y', 12)\n  k.hex(SS, 0.026, 0.014, [off, yo + dir * 0.127, 0])\n}\n\nexport interface ChamberOpts {\n  /** centre-to-centre of the side connections, m */\n  cc: number\n  chamberNps: number\n  connNps: number\n  rating: Rating\n  /** side connection face distance from the chamber axis, m */\n  standout: number\n  /** top end: a blind flange with vent valve, or a flange face left for a GWR head */\n  top: 'vent' | 'gwr'\n  mat: MatKey\n}\n\nexport function chamberDims(o: Pick<ChamberOpts, 'cc'>) {\n  return { yTop: o.cc / 2 + 0.22, yBot: -o.cc / 2 - 0.3 }\n}\n\n/**\n * External level cage: vertical pipe on the origin, side connections facing -X at y = ±cc/2 (weld-neck flanges,\n * faces at x = -standout), blind bottom with drain valve, top either blind + vent valve or bare for a GWR.\n * Returns the top flange face y.\n */\nexport function chamber(k: Kit, o: ChamberOpts) {\n  const { yTop, yBot } = chamberDims(o)\n  const D = pipeOD(o.chamberNps)\n  const f = flangeDims(o.chamberNps, o.rating)\n  const L = f.hubLength + f.raisedFaceHeight\n  pipe(k, [0, yBot + L, 0], [0, yTop - L, 0], o.chamberNps, o.mat, '80')\n  chamberEnd(k, o.chamberNps, o.rating, yBot, -1, true)\n  if (o.top === 'vent') chamberEnd(k, o.chamberNps, o.rating, yTop, 1, true)\n  else flange(k, o.chamberNps, o.rating, [0, yTop, 0], 'y')\n  const fc = flangeDims(o.connNps, o.rating)\n  const Lc = fc.hubLength + fc.raisedFaceHeight\n  for (const y of [-o.cc / 2, o.cc / 2]) {\n    flange(k, o.connNps, o.rating, [-o.standout, y, 0], '-x')\n    pipe(k, [-D / 2 + 0.004, y, 0], [-o.standout + Lc, y, 0], o.connNps, o.mat, '80')\n    k.torus('flangeSteel', pipeOD(o.connNps) / 2 + 0.0008, 0.0025, [-D / 2 - 0.002, y, 0], 'x', 6, 24)\n  }\n  return { yTop, yBot, D }\n}\n\n\n/**\n * Solid instrument flange (blind type) with its raised face at the origin facing -up and the body along +up,\n * blind bolt-hole marks on the back. Returns the back-face offset along up.\n */\nexport function instFlange(k: Kit, nps: number, rating: Rating, up: Axis) {\n  const f = flangeDims(nps, rating)\n  const rf = f.raisedFaceHeight\n  const t = f.thickness\n  const seg = segFor(f.od)\n  k.at([0, 0, 0], A(axisVec(up)), () => {\n    k.lathe('machined', [[0.004, 0], [f.raisedFace / 2, 0], [f.raisedFace / 2, rf]], [0, 0, 0], 'y', seg)\n    k.lathe('flangeSteel', [[f.raisedFace / 2, rf], [f.od / 2, rf], [f.od / 2, rf + t], [0.02, rf + t]], [0, 0, 0], 'y', seg)\n    k.around(f.bolts, f.boltCircle / 2, [0, rf + t + 0.0004, 0], 'y', (p) => k.cyl('pipeBore', f.boltDia * 0.56, 0.001, p, 'y', 10), Math.PI / f.bolts)\n  })\n  return rf + t\n}\n"
    }
  ],
  "categories": [
    "instruments"
  ],
  "meta": {
    "export": "DpTransmitter",
    "anchor": "base",
    "standards": [
      "IEC 60079-1",
      "IEC 61518",
      "ASME B16.5",
      "ISO 5167 impulse lines"
    ]
  }
}