# Coriolis flowmeter

Dual bent-tube Coriolis mass flowmeter: flanged process connections, flow-splitting manifolds, a welded stainless U-shaped case enclosing the tubes, purge / rupture-disc connections, a flow-direction arrow and an integral transmitter with core processor and Ex d head. Tubes hang below the line for liquid or stand above it for gas.

**Standards:** ISO 10790, API MPMS 5.6, ASME B16.5, IEC 60079-1

## Installation

```bash
npx -y https://decktwin.com/cli/deck-twin-0.2.2.tgz add coriolis-flowmeter
```

Copies `instruments/coriolis-flowmeter.tsx`, any helper files it imports and the shared `lib/` into `src/components/deck-twin/`. Needs `three`, `@react-three/fiber` and `@react-three/drei`.

## Usage

```tsx
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
import { Stage } from './components/deck-twin/lib/stage'
import { CoriolisFlowmeter } from './components/deck-twin/instruments/coriolis-flowmeter'

export default function Scene() {
  return (
    <Canvas shadows camera={{ position: [8, 5, 9], fov: 35 }}>
      <Stage>
        <CoriolisFlowmeter />
      </Stage>
      <OrbitControls makeDefault />
    </Canvas>
  )
}
```

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `nps` | `number (NPS, in)` | `3` |  |
| `rating` | `150 \| 300 \| 600 \| 900 \| 1500 \| 2500` | `300` |  |
| `tubes` | `"down" \| "up"` | `"down"` |  |
| `housing` | `"actuatorBlue" \| "stainless" \| "aluminium" \| "equipmentPaintDark"` | `"actuatorBlue"` |  |
| `tag` | `string` | `"FT-1501"` |  |
| `...group` | position, rotation, scale… |  |  |

## Ports

Flange faces in the component's local frame, metres. Origin: pipe centreline, flow along X.

| Id | Label | Position | Direction | Size | Class |
| --- | --- | --- | --- | --- | --- |
| `A` | Inlet | -0.375, 0.000, 0.000 | -1, 0, 0 | 3" | 300 |
| `B` | Outlet | 0.375, 0.000, 0.000 | 1, 0, 0 | 3" | 300 |

## P&ID

Inline box on the line, often with a U or double-U loop drawn inside, joined to an FT / FIT bubble (integral transmitter). Frequently just a generic FE box with "CORIOLIS" or "MASS" written beside it.

tag FE · ISA FE · ISA FT · ISA FIT · ISA FQT · ISA 5.1 (flow element + transmitter); no agreed ISA element shape for Coriolis

ISA 5.1 has no dedicated Coriolis element, so legends differ; read the text beside the box, the instrument index or the datasheet for the technology. Common on chemical injection, condensate, fiscal liquid and test separator outlets. ISA codes here: FQT = totaliser.

Coriolis mass flowmeter specification form (ISA-TR20.00.01) fields and the props they set:

| Field | Prop |
| --- | --- |
| Line size | `nps` |
| Flange rating | `rating` |
| Installation orientation (tubes) | `tubes` |
| Housing material / finish | `housing` |
| Tag number | `tag` |

## Typical suppliers

Emerson (Micro Motion). The geometry is generic and sized from public standards. It is not vendor CAD.

## Source

`instruments/coriolis-flowmeter.tsx`. This is the component file only: it imports shared code, so install it with the command above rather than pasting it alone. https://decktwin.com/r/coriolis-flowmeter.json has every file.

```tsx
// Coriolis mass flowmeter: dual U-tube sensor in a welded stainless case below the line, flow-splitting manifolds, integral Ex d transmitter. ISO 10790, ASME B16.5.
import * as THREE from 'three'
import { Kit, type MatKey } from '../lib/kit'
import { KitMesh, useKit, type GroupProps } from '../lib/mesh'
import { weldBead } from '../lib/parts'
import { port, type Port } from '../lib/ports'
import { textMaterial } from '../lib/textures'
import { flange as flangeDims, pipeOD, pipeWall, type Rating } from '../lib/standards'
import { SS, exHead, tag as tagPlate, flange, pipe, segFor } from './_parts'

export interface CoriolisFlowmeterProps {
  /** nominal pipe size, inches */
  nps?: number
  rating?: Rating
  /** tubes hang below the line (liquid) or stand above it (gas, self-draining) */
  tubes?: 'down' | 'up'
  housing?: MatKey
  tag?: string
}

export const defaults = { nps: 3, rating: 300 as Rating, tubes: 'down' as 'down' | 'up', housing: 'actuatorBlue' as MatKey, tag: 'FT-1501' }

type P = typeof defaults

function dims(p: P) {
  const D = pipeOD(p.nps)
  const f = flangeDims(p.nps, p.rating)
  const endL = f.hubLength + f.raisedFaceHeight
  const L = Math.max(0.42, 0.3 + 0.15 * p.nps) // typical face-to-face
  const neck = 0.04
  const mL = Math.max(0.08, D * 0.9)
  const a = L / 2 - endL - neck - mL / 2
  const w = Math.max(0.06, D * 0.85)
  const wz = Math.max(0.09, D * 1.3)
  const Hc = 0.25 + 0.09 * p.nps // typical case drop
  return { D, endL, L, neck, mL, a, w, wz, Hc }
}

export function ports(props: CoriolisFlowmeterProps = {}): Port[] {
  const p = { ...defaults, ...props }
  const { L } = dims(p)
  return [port('A', 'Inlet', [-L / 2, 0, 0], [-1, 0, 0], p.nps, p.rating), port('B', 'Outlet', [L / 2, 0, 0], [1, 0, 0], p.nps, p.rating)]
}

export function CoriolisFlowmeter(props: CoriolisFlowmeterProps & GroupProps) {
  const { nps, rating, tubes, housing, tag, ...group } = { ...defaults, ...props }
  const p = { nps, rating, tubes, housing, tag }
  const data = useKit((k) => build(k, p), Object.values(p))
  return (
    <group {...group}>
      <KitMesh data={data} />
    </group>
  )
}

export default CoriolisFlowmeter

function caseShape(a: number, w: number, Hc: number) {
  const Rc = Math.max(Math.min(a * 0.9, Hc * 0.45), w + 0.008)
  const ri = Rc - w
  const xo = a + w / 2
  const xi = a - w / 2
  const s = new THREE.Shape()
  s.moveTo(-xo, 0)
  s.lineTo(-xo, -Hc + Rc)
  s.absarc(-xo + Rc, -Hc + Rc, Rc, Math.PI, Math.PI * 1.5, false)
  s.lineTo(xo - Rc, -Hc)
  s.absarc(xo - Rc, -Hc + Rc, Rc, Math.PI * 1.5, Math.PI * 2, false)
  s.lineTo(xo, 0)
  s.lineTo(xi, 0)
  s.lineTo(xi, -Hc + w + ri)
  s.absarc(xi - ri, -Hc + w + ri, ri, 0, -Math.PI / 2, true)
  s.lineTo(-xi + ri, -Hc + w)
  s.absarc(-xi + ri, -Hc + w + ri, ri, -Math.PI / 2, -Math.PI, true)
  s.lineTo(-xi, 0)
  s.closePath()
  return s
}

function build(k: Kit, p: P) {
  const { D, endL, L, neck, mL, a, w, wz, Hc } = dims(p)
  const up = p.tubes === 'up' ? -1 : 1
  for (const s of [-1, 1]) {
    flange(k, p.nps, p.rating, [(s * L) / 2, 0, 0], s > 0 ? 'x' : '-x')
    const x0 = s * (L / 2 - endL)
    const x1 = s * (L / 2 - endL - neck - 0.005)
    pipe(k, [Math.min(x0, x1), 0, 0], [Math.max(x0, x1), 0, 0], p.nps, SS, '80')
    weldBead(k, p.nps, [x0, 0, 0], 'x')
    // splitter manifold casting
    const xm = s * a
    k.lathe(SS, [[D / 2, -mL / 2], [D * 0.62, -mL / 2 + 0.012], [D * 0.72, -mL * 0.2], [D * 0.72, mL * 0.2], [D * 0.62, mL / 2 - 0.012], [D / 2, mL / 2]], [xm, 0, 0], 'x', segFor(D * 1.44))
    // bore into the manifold ends at the flow splitter: two tube inlets side by side (no straight-through path)
    const ri = D / 2 - pipeWall(p.nps, '80')
    const xs = s * (a + mL * 0.1)
    k.lathe('pipeBore', [[ri, mL * 0.4], [ri, 0]], [xs, 0, 0], s > 0 ? 'x' : '-x', segFor(2 * ri))
    k.disc(SS, ri, [xs, 0, 0], s > 0 ? 'x' : '-x', segFor(2 * ri))
    for (const z of [-1, 1]) k.disc('pipeBore', ri * 0.4, [xs + s * 0.0006, 0, z * ri * 0.46], s > 0 ? 'x' : '-x', 24)
  }
  // case top spine between the manifolds
  const rSp = D * 0.42
  k.cyl(SS, rSp, 2 * a, [0, 0, 0], 'x', segFor(2 * rSp))
  // U-tube case (tubes inside) below or above the line
  const m = new THREE.Matrix4().makeScale(1, up, 1).multiply(new THREE.Matrix4().makeTranslation(0, 0, -wz / 2))
  k.extrude(SS, caseShape(a, w, Hc), wz, m, 16)
  for (const s of [-1, 1]) {
    k.cyl(SS, 0.011, 0.016, [s * a * 0.55, -up * (Hc + 0.008), 0], 'y', 12)
    k.hex(SS, 0.024, 0.01, [s * a * 0.55, -up * (Hc + 0.02), 0], 'y')
  }

  // integral transmitter: feed-through neck, core processor, Ex d head
  const yTop = up > 0 ? rSp : Math.max(rSp, Hc)
  k.cyl(SS, 0.024, 0.07, [0, yTop + 0.03, 0], 'y', 16)
  k.cyl(p.housing, 0.046, 0.04, [0, yTop + 0.075, 0], 'y', 28)
  k.torus(p.housing, 0.046, 0.003, [0, yTop + 0.095, 0], 'y', 6, 28)
  exHead(k, [0, yTop + 0.095 + 0.011 + 0.048 - 0.004, 0], p.housing)
  tagPlate(k, p.tag, [-a, -up * Hc * 0.45, wz / 2 + 0.001], 'z', Math.min(w * 0.9, 0.08))
  // flow-direction arrow on the case front (bottom bar)
  const arrow = textMaterial('FLOW  \u2192', '#d8dcdf', '#1d2124', 512, 128)
  k.plane(arrow, Math.min(0.14, 2 * a - w - 0.02), w * 0.45, [0, -up * (Hc - w / 2), wz / 2 + 0.0008])
}
```
