142 lines
6.0 KiB
Markdown
142 lines
6.0 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
npm install
|
||
npm run dev # → http://localhost:5173
|
||
```
|
||
|
||
```bash
|
||
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):
|
||
1. Add `'uvFilter'` to `PartType` in `src/types.ts`.
|
||
2. Add a catalog entry in `src/parts/catalog.ts` — defaults, inspector params
|
||
and `getConnectors()`. Add it to a category in `CATEGORIES` and (if it
|
||
carries water) to `FLOW_PARTS`.
|
||
3. Add a mesh case in `src/components/PartMesh.tsx`.
|
||
4. Give it a resistance in `partResistance()` in
|
||
`src/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 `doser` part + 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().parts` and 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.
|