plotter3d — interactive 3-D trajectory plots + report-figure generators

Purpose

Two unrelated jobs in one file. Plotter3D draws interactive 3-D base-CoM / EE trajectories
with pointing-axis quivers (a debugging viewer). The module-level fig_* functions are
standalone report-figure generators that rebuild their geometry live from parameters.yaml
and write the week-2 paper figures.

Role in the system

  • A standalone visualization helper in utils — not on the control/guidance hot path.
  • Plotter3D consumes a log object exposing p_actual/p_desired/z_actual/z_desired
    (and optional p_e*/z_e* for EE) — the interactive eyeball view, no pipeline provenance.
  • The fig_* generators pull live geometry from orbit (OrbitGenerator) and mesh (Mesh)
    via _setup, and the coverage figure reuses geometry’s safe_normalize and the mesh ray-cast
    scene — so nothing is hard-coded.
  • Run directly: python utils/plotter3d.py writes the three figures into docs/week2/report_v2/figures/.

Not the sanctioned figure path

Pipeline figures render through analysis/plotter.py (the matplotlib-only home, hook-enforced).
This file imports matplotlib directly because it lives in utils/, not GNC//validation/, and
these are one-off paper schematics built outside the run→log→analyze pipeline — they carry no
scoreboard provenance. See orchestrator / .claude/rules/MEASUREMENT.md for the catalog path.

Inputs / Outputs

  • Plotter3D in: a log/record with p_actual, p_desired, optional z_* and p_e*/z_e*
    arrays (shape (N,3)); out: an interactive matplotlib window (plt.show()), no file.
  • fig_* in: an output path + figure-only knobs (helix n_rev, frame/point counts); out:
    PNG/GIF on disk, plus a dict of the live-computed annotation values (rho, r_cam, coverage).

Key methods / functions

  • Plotter3D.plot_combined — base-CoM + every available EE trajectory together, each with quivers — utils/plotter3d.py:164
  • Plotter3D.plot_base_com — actual vs desired base-CoM with base-pointing quivers — utils/plotter3d.py:83
  • Plotter3D.plot_quiver — the strided pointing-axis quiver primitive (shared by the plots) — utils/plotter3d.py:65
  • fig_standoff — POSE-mode standoff/pointing schematic (sec 5.3) — utils/plotter3d.py:340
  • fig_coverage — cumulative inspection-coverage GIF+PNG (sec 4.4) — utils/plotter3d.py:405
  • fig_progress — progress-coloured helix + along-/cross-track error panel (sec 1) — utils/plotter3d.py:526
  • _setup — load config + orbit + mesh once; n_rev override is figures-only — utils/plotter3d.py:327

Footguns

The standoff schematic is deliberately not to scale

r_cam is only ~5% of rho; a to-scale view collapses the standoff construction. fig_standoff
draws r_cam exaggerated but annotates the live-computed rho/r_cam so the caption stays
numerically correct. Read the numbers, not the proportions. (utils/INSIGHTS.md [plotting])

Coverage fraction is a real estimator, not a cartoon

fig_coverage samples the mesh area-uniformly (Mesh.sample), so seen/total is an unbiased
estimate of inspected-area fraction, and the predicate is the codebase’s own FOV-cone + depth-band

  • unoccluded-LoS test (not a bespoke approximation). The colorbar is pinned to [0, vmax] so every
    frame shares one axis. (utils/INSIGHTS.md [plotting])

Pseudocode (report-figure entrypoint)

__main__: switch backend -> Agg, mkdir figures/
  fig_standoff(...)   # schematic geometry, exaggerated r_cam, live rho annotation
  fig_coverage(...)   # marking pass over the helix -> per-frame cumulative counts -> GIF + final PNG
  fig_progress(...)   # helix coloured by orbit progress + 2D along-/cross-track error panel
# each fig_* calls _setup() -> live (cfg, orbit, mesh); notation tracks final.tex secs 1, 4.4, 5.3

orbit · mesh · geometry · plotter · orchestrator · terminology