6.0 KiB
Hydro Builder
An interactive, browser-based 3D builder for hydroponic / water-flow systems. Design lattice towers, grow walls, pipe networks, pumps, reservoirs and custom water paths — with live, approximate flow simulation.
Built with React + TypeScript + Vite, React Three Fiber / Drei, Zustand, and TailwindCSS.
Quick start
npm install
npm run dev # → http://localhost:5173
npm run build # typecheck + production build (dist/)
npm run preview # serve the production build
npm run typecheck # tsc only
On first launch the app loads a working demo: reservoir → pump → riser → overhead return → emitter spraying back into the tank, plus a grow tower, tray and lattice to play with. Your work autosaves to the browser and is restored on the next visit.
Using the builder
| Action | How |
|---|---|
| Place a part | Click it in the left library, then click the ground (Shift-click stamps copies). Or drag it from the library onto the canvas. |
| Move a part | Drag it in the scene. In Front/Side views, dragging moves vertically too. |
| Raise / lower | E / Q (or edit Y in the inspector) |
| Rotate | R (90° around Y), Shift-R reverse, or inspector fields/buttons |
| Duplicate / delete | D / Delete, or inspector buttons |
| Undo / redo | ⌘Z / ⇧⌘Z (or toolbar) |
| Measure | Toolbar 📏, then click two points |
| Snap to grid | Toolbar ⌗ (0.25 ft grid) |
| Connect pipes | Drop a part near another part's connector — it snaps. Green dot = joined, amber = open. |
| Views | Toolbar: 3D orbit / Top / Front / Side (2D orthographic) |
| Water animation | Toolbar 💧 Flow |
| Save / Load | Toolbar — named projects in browser storage |
| Export / Import | Toolbar — design JSON file |
How the simulation works
The system is treated as a graph:
- Nodes — connection points; connectors that touch are merged (union-find). Every flow-carrying part also gets an internal node so 3-way tees, tanks, towers etc. work uniformly.
- Edges — part bodies (pipes, elbows, valves, …), each with a hydraulic resistance.
- Pump — pressure source. It checks its inlet reaches a reservoir, then traverses downstream collecting resistance, static head (highest point above the pump), open ends, dead ends and loops.
Approximate delivered flow:
Q = rated_GPH × headFactor × resistanceFactor
headFactor = clamp(1 − head / maxHead, 0, 1)
resistanceFactor = 1 / (1 + R / 30)
R(pipe) ≈ length / diameter⁴
R(elbow) ≈ 1.2 × (angle/90) / diameter²
R(valve) ≈ 0.3 + restriction(% open); closed = blocks flow
Diagnostics surfaced in the bottom panel: missing source, disconnected pump, pump too weak for the elevation (head ≥ max head), bottlenecks, open pipe ends (leaks), dead ends, closed valves, loops. Click a diagnostic to select the offending part. Flow direction is rendered as animated arrows; flowing pipes tint blue and the rate scales arrow speed.
This is deliberately simple steady-state math — not CFD — and is designed to be swapped out later (see below).
Project structure
src/
├── types.ts # Shared domain types (parts, sim, project file)
├── parts/
│ └── catalog.ts # ★ Part catalog: defaults, params, connectors
├── store/
│ └── builderStore.ts # Zustand store + undo/redo (BuilderState)
├── simulation/
│ └── flowSimulator.ts # Graph build + pump physics + warnings
├── utils/
│ ├── connectors.ts # World-space connector math, snap logic
│ ├── serializer.ts # Save/load/export/import (ProjectSerializer)
│ ├── demoProject.ts # Starter scene
│ └── dragState.ts # Transient drag context
├── components/
│ ├── SceneCanvas.tsx # R3F canvas, cameras, grid, drag plane, measure
│ ├── PartMesh.tsx # Geometry for every part type + selection/drag
│ ├── FlowArrows.tsx # Animated flow-direction arrows
│ ├── Toolbar.tsx # Project actions, tools, view presets
│ ├── PartLibrary.tsx # Left sidebar
│ ├── PropertiesPanel.tsx # Right sidebar inspector
│ └── StatusPanel.tsx # Bottom simulation status
├── App.tsx # Layout, shortcuts, autosave, drag-drop
└── main.tsx
Conventions: world units are feet (1 grid cell = 0.5 ft), pipe diameters in inches, flow in GPH (shown with L/h). Rotations are radians (XYZ euler) in state, degrees in the UI.
How to extend
Add a new part type (e.g. a UV filter):
- Add
'uvFilter'toPartTypeinsrc/types.ts. - Add a catalog entry in
src/parts/catalog.ts— defaults, inspector params andgetConnectors(). Add it to a category inCATEGORIESand (if it carries water) toFLOW_PARTS. - Add a mesh case in
src/components/PartMesh.tsx. - Give it a resistance in
partResistance()insrc/simulation/flowSimulator.ts.
That's it — placement, snapping, drag, save/load, inspector and simulation pick it up automatically.
Improve the physics: everything lives in flowSimulator.ts. The graph
build is separate from the flow estimate, so you can replace the single-pass
estimate with e.g. Hardy-Cross iteration or a linear solver over the same
graph without touching the UI.
Planned extension points (the architecture already supports them):
- Nutrient dosing — add a
doserpart + per-edge concentration tracking in the simulator (it already knows flow per part). - Plant growth zones / lighting / timers — new part categories; timers can
gate
partResistance()(a valve already shows how blocking works). - Bill of materials — iterate
useBuilder.getState().partsand group by type/params; the catalog has labels and units. - Parts marketplace / AI assistant — the project JSON (
ProjectFile) is a complete, validated serialization format for sharing and generation.
