Each component is one .tsx file in a category folder of registry/. It also has an entry in that folder's _meta*.ts. The file must still compile after someone copies it into their own project, so it imports only these:
reactthreeandthree/examples/jsm/*@react-three/fiberand@react-three/drei../lib/*, the shared core- other registry components. Install them with the component.
./_somethinghelper files in the same category folder. These ship with every component that imports them.
npx tsx scripts/build-registry.ts --check rejects any other import.
File shape
Start with the gate valve (registry/valves/gate-valve.tsx), the pump (registry/rotating/centrifugal-pump.tsx) and the separator (registry/vessels/horizontal-separator.tsx).
// Gate valve: one line saying what it is and the standards it follows.
import { Kit } from '../lib/kit'
import { KitMesh, useKit, type GroupProps } from '../lib/mesh'
import { port, type Port } from '../lib/ports'
import { faceToFace, type Rating } from '../lib/standards'
export interface GateValveProps {
nps?: number
rating?: Rating
/** stem travel, 0 closed … 1 open */
open?: number
}
export const defaults = { nps: 6, rating: 300 as Rating, open: 1 }
export function ports(p: GateValveProps = {}): Port[] {
const { nps, rating } = { ...defaults, ...p }
const L = faceToFace('gate', nps, rating)
return [port('A', 'End A', [-L / 2, 0, 0], [-1, 0, 0], nps, rating), port('B', 'End B', [L / 2, 0, 0], [1, 0, 0], nps, rating)]
}
export function GateValve({ nps = defaults.nps, rating = defaults.rating, open = defaults.open, ...group }: GateValveProps & GroupProps) {
const data = useKit((k) => build(k, nps, rating), [nps, rating])
return (
<group {...group}>
<KitMesh data={data} />
</group>
)
}
export default GateValveThe exports, in order:
| Export | What |
|---|---|
interface XProps |
Every prop is optional. Lengths are in metres, nps is in inches, rating is an ASME class, power is in kW and angles are in degrees. |
defaults |
A plain object holding a value for every prop. Pick a typical real configuration. |
ports(props) |
Flange faces in the local frame, using the same maths as the geometry. Leave it out if the item has no connections. |
X |
The React component. It spreads the remaining props onto a <group> so users can pass position, rotation and so on. |
default |
The same component. |
Prop names to avoid, because Object3D already uses them: id, name, type, position, rotation, scale, visible, up, layers, children, matrix, quaternion and userData. Use diameter, not id.
Frame and origin
| Anchor | Origin | Use for |
|---|---|---|
base |
Centre of the footprint at deck level, y = 0. Nothing goes below y = 0 unless the item really does (caisson pumps, jackets, risers). | Equipment, structures, packages |
inline |
On the pipe centreline. Ports A at -X and B at +X, both on the X axis. |
Valves, strainers, inline meters, spools |
fitting |
Body centre. Ports wherever the geometry puts them. | Elbows, tees, PSVs |
Axes: +Y is up, and the main axis of a horizontal item runs along +X. When an item has an obvious front, such as a cab door, a control panel or a ladder, face it +Z.
Ports
port(id, label, position, direction, nps, rating?, kind?)
positionis the centre of the raised face. The assembler bolts a pipe flange onto it.directionis a unit vector pointing out of the face.- Give ports short lowercase ids:
inlet,outlet,suction,discharge,gas,liquid,water,psv,drainandvent. Inline items useAandB. Keep the ids stable across props. kinddefaults toprocess, which means it should be connected in a plant. Userelief,vent,drain,utilityorinstrumentfor connections that can be left open.- Every port needs a real flange at that point in the geometry. Use
nozzle(), orflange()plus a neck.
Geometry
Build each static assembly into one merged mesh with
useKit((k) => …, deps)and<KitMesh>. UseKitprimitives such asbox,rbox,cyl,rod,lathe,torus,sphere,dome,tube,pipe,bend,extrude,boltCircleandhandwheel.Reuse the parts in
lib/parts.ts:- flanges and joints:
flange,blindFlange,flangedJoint; - piping:
nozzle,pipe,pipeRun,weldBead; - vessels and access:
manway,saddle,ladder,ringPlatform,handrail,grating,checkerPlate; - steel and skids:
member(any EN or AISC section),skid; - machinery:
motorandmotorSize(IEC frames),couplingGuard; - small details:
gauge,junctionBox,liftingLug,tagPlate.
- flanges and joints:
Moving parts go in a separate Kit. Spin them with
<Rotors>(lib/rotors.tsx), or use a group transform driven by a prop such asopen,sleworluff.Use materials by name from
lib/materials.ts, or callpaint('#hex'). Textures come fromlib/textures.ts: grating, dials, tag plates, signs, hazard stripes, checker plate and louvres.Size everything from
lib/standards:Function Standard pipeOD,pipeWallASME B36.10 flangeASME B16.5 / B16.47 elbow90,tee,reducerASME B16.9 faceToFaceASME B16.10 psvAPI 526 SECTIONSEN / AISC steel sections iecMotorIEC 60072 motor frames ACCESSISO 14122 access rules shellThickness,headDepthASME VIII SERVICE,RALISO 14726 colours When a value comes from a typical design rather than a standard, put
// typicalnext to it.Keep the geometry efficient. Use 24–48 radial segments for large cylinders and 8–12 for small rods and bolts. Use no per-frame allocations.
Realism checklist
- Dimensions come from the standard, and they change with the props.
- Every port has a flanged nozzle with studs and nuts.
- The item has nameplates or tag plates, lifting lugs, earth bosses, drains, vents and instrument tappings where a real one would.
- Access (ladders, platforms, handrails) follows ISO 14122 wherever an operator needs to reach a valve, nozzle or instrument above 2 m.
- Bases have base plates and anchor bolts or skid steel. Nothing floats, everything touches what holds it, and there is no z-fighting.
- The paint follows practice: equipment light grey (RAL 7035), machinery green or blue, safety yellow for guards and handrails, and red for firewater.
Comments
Write one header line per file saying what the item is and which standards it follows. Add short comments only where the maths isn't obvious. A short /** unit */ on a prop is fine. Don't write long JSDoc.
Metadata
Add an entry to the category's _meta*.ts:
{
name: 'gate-valve', // kebab-case, unique, also the CLI name
title: 'Gate valve',
file: 'gate-valve.tsx',
export: 'GateValve',
description: 'Two or three sentences: what it is, what is modelled, which props matter.',
standards: ['API 600', 'ASME B16.10'],
vendors: ['Velan', 'Flowserve'], // typical suppliers of this type, not vendor CAD
tags: ['isolation', 'manual'],
anchor: 'inline',
params: [ /* live controls: number (min, max, step, unit), select, bool, nps, rating, color, text */ ],
preview: { /* optional prop overrides for the preview */ },
}Verify
npx tsc -p tsconfig.app.json --noEmit
npx tsx scripts/build-registry.ts --check <category>
node scripts/shoot.mjs <name> [--ports] [--props '{"nps":12}'] [--view 1,0.4,0.2]shoot.mjs writes .snaps/<name>.jpg. Look at it at default props and at least one other setting, and check the ports with --ports.