Skip to the content
PCBKitNew board
2 layers70 × 54 mmPsdb, the printed circuit board in 3D

Autonomous indoor 3D scanning drone — RK4 physics, SLAM, frontier+coverage exploration, semantic reconstruction. Coverage parity on estimated pose vs ground truth.

Size
70 × 54 mm
Copper layers
2
Thickness
1.6 mm
Parts
27
Nets
19
Pads
88
Tracks, vias
303, 11
Pours
1
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
20 Jun 2026
Updated
25 Jun 2026

Overview

Psdb is an open-source printed circuit board by sypherdark, from the sypherdark/AetherScan repository on GitHub, published under the MIT licence. It is a 2-layer board of 70 × 54 mm with 27 parts on 19 nets, routed with 303 tracks and 11 vias.

Its parts include 13 capacitors, 9 resistors, 3 ICs and 2 inductors. It belongs with the robots and motor control boards of this library.

README

README.md

AetherScan — Autonomous Indoor 3D Scanning Drone

A drone that flies itself through an unknown indoor space, maps it in real time, and exports a 3D reconstruction.

AetherScan is a from-scratch indoor drone autonomy stack — 6-DoF physics, SLAM, frontier + coverage exploration, and semantic 3D reconstruction — built to the point where it demonstrates coverage parity flying on its own drifting pose estimate vs. flying on ground truth, in simulation. The remaining gaps to real hardware are identified, measured, and tracked in ROADMAP.md and REALWORLD_READINESS.md.

What actually runs: the redwood_sim/ Python backend (all physics, sensing, mapping, planning) streaming state over a WebSocket to a Next.js + React Three Fiber dashboard. You do not need ROS 2 or Gazebo to run AetherScan. The aetherscan_ws/ ROS 2 workspace is scaffolding for the eventual hardware-transfer target — see Repository layout.


How it works

Two independent processes with a single clean data contract between them.

text
┌──────────────────────────── redwood_sim/ (Python) ────────────────────────────┐
│                          ALL computation lives here                            │
│                                                                                │
│   physics.py ──► controls.py ──► navigation.py ──► discovery_map.py            │
│   (RK4 6-DoF      (cascaded      (frontier +        (log-odds occupancy        │
│    @ 500 Hz)       PID)           coverage A*)       grid, 0.2 m)              │
│        │              │               │                   │                    │
│   sensors.py    state_estimation.py   scan_matching.py    exporters.py         │
│   (168-ray       (drifting pose       (Olson 2009         (PLY / GLB / SVG     │
│    LiDAR sim)     estimate)            SLAM correction)     deliverables)       │
│                                                                                │
│                     bridge/server.py  ──►  WebSocket :8765  (20 Hz state)      │
└────────────────────────────────────────────────────────────────────────────────┘
                                      │
                          JSON over WebSocket
                                      │
┌──────────────────────── dashboard/ (Next.js + R3F) ───────────────────────────┐
│                       DISPLAY ONLY — zero physics, zero planning               │
│                                                                                │
│   ScanEnvironment   PointCloudRenderer   DiscoveredMap   PathVisualization     │
│   (loads scene GLB) (streams recon cloud)(occupancy grid)(planned path)        │
│                          MissionControl (start / stop / god-mode)              │
└────────────────────────────────────────────────────────────────────────────────┘

Invariant #0: the dashboard never computes physics or planning. It is a terminal. Every number it draws was produced in Python and shipped over the socket.

A single mission loop: the drone starts with an empty map (everything unknown). Each tick it raycasts a 168-ray LiDAR against the scene, folds the hits into a log-odds occupancy grid, picks the next frontier (using a FUEL-style global tour to avoid re-walking corridors), plans an obstacle-inflated A* path to it, and flies there under a cascaded PID controller — while a correlative scan-matcher bounds the drift in its self-estimated pose. Scanned points accumulate into a voxel-deduplicated cloud you can export as a colored PLY, a Poisson GLB mesh, and an SVG floor plan.


Quick start (no ROS, no Docker, no GPU)

Requires Python 3.11+ (Apple Silicon: install via Homebrew — the system 3.9 segfaults on the Open3D BVH build) and Node 18+.

1. Set up the physics backend

bash
cd redwood_sim
./setup-macos.sh        # creates .venv (Python 3.11) and installs requirements.txt
#  …or manually:
#  python3.11 -m venv .venv && .venv/bin/pip install -r requirements.txt

2. Run the two processes

One-shot convenience script (starts the bridge headless, then the dashboard):

bash
./run-aetherscan.sh --dashboard            # from repo root
./run-aetherscan.sh --dashboard --scene room_0    # pick a scene
# → http://localhost:3000

Or run them by hand in two terminals:

bash
# Terminal 1 — physics bridge (WebSocket ws://127.0.0.1:8765)
cd redwood_sim
.venv/bin/python -m bridge --scene apartment_1

# Terminal 2 — dashboard
cd dashboard
npm install
npm run dev
# → http://localhost:3000

Open http://localhost:3000, pick a scene, hit Start Mission, and watch the drone explore. The fuchsia GOD button enables ~3× time acceleration.

Note on scene meshes: the dashboard's visual .glb meshes and the backend's collision meshes are not in the repo (they're hundreds of MB, generated from the Meta Replica dataset). The physics bridge runs fine without them; the 3D viewport will be empty until you generate the assets — see Scene assets.

3. Run the tests

bash
cd redwood_sim
.venv/bin/python -m pytest tests -q        # 8 state-estimation regression tests

The WebSocket protocol

The bridge speaks JSON over ws://127.0.0.1:8765, broadcasting state at 20 Hz.

MessageDirectionPayload
helloserver→clientvisual_mesh_url, scene_bounds (ROS ENU frame, never pre-transformed)
scene_changedserver→clientsame as hello, on scene switch
state (20 Hz)server→clientposition, quaternion, velocity, tilt, altitude, coverage_pct, total_points, path, discovery_map, map_points (new points since last frame), localization{ mode, pos_drift_m, yaw_drift_deg, slam_corrections, slam_match_score, slam_health }, god_mode
set_sceneclient→serverswitch the active scene
set_god_modeclient→servertoggle 3× time acceleration
export_scanclient→serverrun the PLY/GLB/SVG export pipeline async; replies export_started / export_complete{ urls, files, errors }

Reconstruction points are streamed as deltas (map_points = new-since-last-frame), and the frontend uploads only new points into a preallocated 500k buffer — it never rebuilds the cloud. All server sends pass through a single asyncio.Lock so a scene-switch can't race the 20 Hz broadcast and drop the connection.

Coordinate convention: everything in Python is Z-up ROS / ENU (X=forward, Y=left, Z=up). The Three.js layer applies rosToThree(x,y,z) = [x, z, -y] exactly once, at the scene group. scene_bounds cross the wire in the ROS frame, untouched.

Bridge CLI: python -m bridge --scene <id> [--host 127.0.0.1] [--port 8765] [--rate 20] [--voxel 0.03] [--dt 0.002]


What's in the autonomy stack

ModuleWhat it doesNotable detail
core/physics.py6-DoF quadrotor rigid body, RK4 @ 500 Hz~1.45 kg frame, drag, ground effect, Dryden wind; collision resolved per micro-step to prevent tunneling
core/controls.pyCascaded PID: position→velocity→attitude→thrustCorrect ZYX-Euler world→body tilt mapping (a swapped-axis bug previously made it fly perpendicular to its command)
core/sensors.py168-ray LiDAR (7 rings × 24), range/normal noiseConfigurable FOV (360° RPLIDAR baseline; 87° = D435 camera-class)
core/discovery_map.pyLog-odds occupancy grid, 0.2 mFloor hits are FREE at flight altitude; noisy returns can't flip a cell; compact body model fits 0.8 m doorways
core/navigation.pyFrontier + coverage exploration, A* path-followingFUEL-style global tour ordering: +15–56 % coverage per meter
core/state_estimation.pyDrifting, noisy pose estimate (the sim-to-real seam)config.use_estimated_pose — off for the clean demo
core/scan_matching.pyCorrelative scan-matcher (Olson 2009) + keyframe SLAMBounds drift to 0.04–0.10 m vs. 0.14→0.33 m unbounded
core/exporters.pyPLY (semantic-colored) / GLB (Poisson) / SVG floor planWritten to dashboard/public/exports/

Headline result: with use_estimated_pose=True, the drone flies entirely on its own estimate — discovery map, frontier goals, path-following, and trajectory setpoints all live in the estimated frame — and reaches coverage parity with ground-truth navigation (apartment_1 35.2 % vs 37.0 %; room_0 89.8 % vs 87.2 %), with lower tilt and fewer wall contacts. Full audit: REALWORLD_READINESS.md.


Hardware — the physical drone

The simulation defines a real aircraft, and hardware/ designs it to match — kept in accordance with the software by a checker that fails if the CAD ever drifts from the physics model (hardware/cad/check_against_software.py).

  • Airframe — a 360 mm quad-X (1.45 kg, RPLIDAR A2 + RealSense D435i + Jetson Orin Nano + Pixhawk/PX4), modelled parametrically in code with build123d. See hardware/cad/.
  • Design reviews — converged through structured multidisciplinary design reviews (aerodynamics, mechanical, electrical, software, systems) to a deployment-ready sign-off, with the sim's inertia reconciled to the buildable airframe and the controller re-validated. See hardware/design/.
  • Electrical — the Power & Sensor Distribution Board is defined in code (atopile) and laid out / 3D-rendered / Gerber-exported with KiCad. See hardware/electrical/.
  • Build plan — funding-staged H0→H4 roadmap; design complete, parts not yet bought. hardware/ROADMAP_HARDWARE.md.

A 2-page investor brief built from real captured system output lives in presentation/.


Scene assets

AetherScan ships with 18 Meta Replica scenes (apartment_0–2, frl_apartment_0–5, office_0–4, room_0–2). For licensing and size reasons the dataset and generated meshes are not committed. To populate the 3D viewport:

  1. Obtain the Replica dataset and place/symlink scenes under redwood_sim/data/replica/<id>/.
  2. Generate dashboard visual meshes and backend collision meshes:
    bash
    python3 scripts/convert_replica_to_glb.py          # → dashboard/public/meshes/<id>.glb
    python3 scripts/build_collision_meshes_fast.py     # → <id>_collision.ply (~120k tris)

The physics bridge runs without these (it falls back to built-in/procedural geometry); they only affect what the dashboard renders and what the backend raycasts against.


Repository layout

text
redwood_sim/          ← THE WORKING SYSTEM (Python: physics, sensing, mapping, planning, SLAM)
  core/               ← physics, controls, sensors, discovery_map, navigation, state_estimation, scan_matching, exporters
  bridge/server.py    ← WebSocket bridge :8765
  simulation/engine.py← fixed-timestep mission loop
  tests/              ← state-estimation regression suite
dashboard/            ← Next.js + React Three Fiber UI (display only)
  src/components/three/  ← scene, point cloud, occupancy, path, drone renderers
  src/lib/            ← WebSocket client, scene registry, ROS↔Three transforms
scripts/              ← Replica → GLB conversion, collision-mesh builder, semantics
hardware/             ← THE PHYSICAL DRONE (design, CAD, electrical) — see below
  design/             ← multidisciplinary design reviews & converged spec
  cad/                ← parametric airframe (build123d) + software-accordance checker
  electrical/pcb/     ← code-defined PCB (atopile) + KiCad layout/render/gerbers
  specs/ · bom/       ← system spec, mass/power/inertia budgets, real-parts BOM
presentation/         ← 2-page investor brief (PDF) built from real system output
aetherscan_ws/        ← ROS 2 (Humble) workspace — HARDWARE-TRANSFER TARGET, not required to run the sim
docker/               ← optional containerized sim + dashboard

About the ROS 2 workspace

aetherscan_ws/ contains a full ROS 2 package set (description, gazebo, navigation, perception, slam, control, mission, bringup). It is the intended deployment target for porting the autonomy stack onto a real airframe (RViz config, URDF/Xacro drone model, Gazebo worlds, launch files). It is not the path that runs the simulation you see in the dashboard — that's redwood_sim/. Treat the ROS 2 side as scaffolding under active development, not a finished product.


Research grounding

The algorithms are drawn from the literature and measured against it:

  • FUEL (Zhou et al., RA-L 2021) — global tour ordering of frontier vantages.
  • Olson (2009) — correlative scan matching for the SLAM correction.
  • Emesent Hovermap field practice — SLAM-health-aware speed gating.
  • DARPA SubT (CERBERUS, CoSTAR/NeBula) — uncertainty-aware reactive standoffs (wall grazes 140→52 on the cluttered scene).

Honest status — what's not done

  1. Global loop closure — SLAM bounds drift locally but there's no pose-graph; long (200 m+) missions still accumulate global error.
  2. Sensor latency / rate decimation — sensors currently fire in lockstep with the control loop; real sensors run async with 20–50 ms latency.
  3. Motor model — no spin-up time constant, thrust saturation curve, or actuator delay.
  4. Controller hardware tuning — PID gains tuned against perfect feedback; needs a noise + latency injection / hardware-in-the-loop pass.
  5. Camera-only support — an 87° FOV halves coverage; camera-only hardware needs yaw-sweep scanning + short-term obstacle memory.
  6. ROS 2 deployment — aetherscan_ws/ is scaffolding, not a validated flight stack.

See ROADMAP.md for the phased plan.


License

MIT — see LICENSE.

Parts

27
QtyValueFootprintReferences
5100nF ±10%YAGEO_CC0603KRX7R9BB104:C0603C1, C2, C6, C7, C13
210kΩ ±1%UNI_ROYAL_0603WAF1002T5E:R0603R1, R5
21µF ±10%Samsung_Electro_Mechanics_CL05A105KA5NQNC:C0402C5, C11
222µF ±20%HRE_CGA0805X5R226M350MT:C0805C3, C8
24.7kΩ ±1%UNI_ROYAL_0402WGF4701TCE:R0402R7, R8
247µF ±10%Murata_Electronics_GRM32ER61C476KE15L:C1210C4, C9
1Texas_Instruments_LMR33630ADDAR:ESOP-8_L4.9-W3.9-P1.27-LS6.0-TL-EPU1
1Texas_Instruments_TPS568230RJER:QFN-20_L3.0-W3.0-P0.45-BL-EP1.0U2
1Texas_Instruments_INA226AIDGSR:MSOP-10_L3.0-W3.0-P0.50-LS5.0-BLU3
1100kΩ ±1%UNI_ROYAL_0402WGF1003TCE:R0402R4
113kΩ ±1%FOJAN_FRC0603F1302TS:R0603R3
12.2nF ±10%FH_0402B222K500NT:C0402C10
12mΩ ±1%Milliohm_HoLLR2512_3W_2mR_1:RES-SMD_L6.4-W3.2-1R9
140.2kΩ ±1%UNI_ROYAL_0603WAF4022T5E:R0603R2
1470µF ±20%AISHI_ERJ1HM471G16C36T:CAP-TH_BD10.0-P5.00-D1.0-FDC12
173.2kΩ ±1%UNI_ROYAL_0402WGF7322TCE:R0402R6
1L 2.2µH ±20%cjiang_FTC252012S2R2MBCA:IND-SMD_L2.5-W2.0_MHCHL2520L2
1L 4.7µH ±20%cjiang_FTC252010S4R7MBCA:IND-SMD_L2.5-W2.0-VLS252012CX-150ML1

Questions

How big is the Psdb PCB?

It measures 70 × 54 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Psdb use?

27 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Psdb KiCad files?

From the sypherdark/AetherScan repository on GitHub, at the commit this page was made from: the board is hardware/electrical/pcb/elec/layout/psdb/psdb.kicad_pcb and its schematic hardware/electrical/pcb/elec/layout/psdb/psdb.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Psdb design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

Similar boards

  • 2 layers47.6 × 27.3 mmMmc usb 1, the printed circuit board in 3D

    Mmc usb 1

    rusefi/rusefi

    rusefi - GPL internal combustion engine control unit

    27 parts1.2k
  • 2 layers77.4 × 77.4 mmCORAZON LEDS CON FADE, the printed circuit board in 3D

    CORAZON LEDS CON FADE

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    27 parts915
  • 2 layers44.2 × 29.3 mmI2c tiny usb, the printed circuit board in 3D

    I2c tiny usb

    harbaum/I2C-Tiny-USB

    Cheap and simple I²C to USB interface

    27 parts454
  • 2 layers22 × 42 mmNvme bm to e, the printed circuit board in 3D

    Nvme bm to e

    CRImier/MyKiCad

    My KiCad boards. Can't be bothered uploading individually anymore, so here you go.

    27 parts253GPL-3.0
  • 2 layers80 × 61 mmPicoProducer, the printed circuit board in 3D

    PicoProducer

    pjgpetecodes/pico-streamdeck

    This project is an OBS Controller using a Raspberry Pi Pico and CircuitPython.

    27 parts230MIT
  • 2 layers186 × 45 mmAddOnD, the printed circuit board in 3D

    AddOnD

    freeDSP/freeDSP-aurora

    freeDSP ADAU1452 with 8 analog input, 8 analog outputs, S/P-DIF I/O, ADAT I/O, USB Audio Class2, WiFi, Bluetooth

    27 parts226CC-BY-SA-4.0
  • 2 layers29.6 × 40.6 mmDbwifi revB, the printed circuit board in 3D

    Dbwifi revB

    ugcs/ddc

    Drone Show Software is software designed for drone shows, allowing flight path planning as well as control of dozens of drones simultaneously in a synchronized manner.

    27 parts223