# CloudView A small, **generic** point-cloud viewer. It listens on a socket, accepts newline-delimited JSON (NDJSON) from any number of producers, and renders the combined cloud in an interactive [Open3D](https://www.open3d.org/) window. It is not tied to any one robot: anything that can write JSON lines to a socket — a Crazyflie, a rover, a LIDAR rig, a log replay — gets live 3D visualization for free. RangeView is the first producer (its 2D map stays the at-a-glance live diagnostic; the full 3D cloud is streamed here), but the wire format is the only contract, so other systems can feed the same viewer. ``` producers (any language) viewer ┌───────────────────────┐ NDJSON over ┌──────────────────────┐ │ rangeview ───────────┼── tcp:// ────────▶ CloudView (Open3D) │ │ rover-2 ───────────┼── unix:// ───────▶ - merges all sources │ │ lidar-rig ───────────┼──────────────────▶ - colour by height │ └───────────────────────┘ └──────────────────────┘ ``` ## Install & run ```sh # with uv (resolves from the committed uv.lock) uv run main.py # listen on tcp://127.0.0.1:9870 uv run main.py -l tcp://0.0.0.0:9870 # accept from other machines # or a plain venv python -m venv .venv && . .venv/bin/activate pip install -r requirements.txt # open3d + numpy python -m cloudview ``` Listen addresses (repeatable, TCP and/or Unix): ```sh uv run main.py -l tcp://0.0.0.0:9870 -l unix:///tmp/cloud.sock ``` Viewer keys: **C** toggle colour (by height / per-source), **R** refit the camera, **B** cycle background, mouse to orbit/zoom/pan. ## Wire protocol (NDJSON) One JSON object per line over a stream socket. `source` keys a distinct cloud, so one viewer shows several robots at once. Coordinates are floats in whatever frame the producer uses. | message | fields | effect | |---------|--------|--------| | `hello` | `source`, `name` | announce/label a stream | | `points` | `source`, `points` (`[[x,y,z],...]`), `frame`, `color` (optional `[r,g,b]` 0..1) | append a batch | | `clear` | `source` | drop that source's cloud | | `pose` | `source`, `position` `[x,y,z]`, `yaw` (deg) | record robot pose | Points are batched per message to keep overhead low. If a source supplies no `color`, the viewer colours its points by height; otherwise the given colour is used as a solid. ### Producing from any language It is just JSON lines — test it with `nc`: ```sh printf '%s\n' \ '{"type":"hello","source":"demo","name":"nc test"}' \ '{"type":"points","source":"demo","points":[[0,0,0],[1,0,0.5],[0,1,1]]}' \ | nc 127.0.0.1 9870 ``` Python producers can reuse RangeView's `cloud_publisher.CloudPublisher` (a non-blocking client with auto-reconnect and full-cloud replay), or just write `json.dumps(msg) + "\n"` to a socket. ## Layout - `cloudview/protocol.py` - NDJSON encode/decode + address parsing. - `cloudview/store.py` - thread-safe per-source cloud store. - `cloudview/server.py` - TCP/Unix socket server; one reader thread per producer. - `cloudview/viewer.py` - Open3D render loop (height/per-source colouring). - `cloudview/app.py` - CLI entry point. Networking and rendering are decoupled: the server fills the store from any producer; the viewer polls the store and rebuilds geometry only when it changes. The server/protocol layers have no Open3D dependency, so they are testable headless.