Take the scan waypoints only from the CSV file, never generate them
This commit is contained in:
parent
ab9fa3dbb0
commit
a21be0d4e8
|
|
@ -206,19 +206,17 @@ homing_timeout_s = 65
|
|||
|
||||
[Scan]
|
||||
; Capture scan grid (the (yaw,pitch) waypoints auto-sweep steps through).
|
||||
; If grid_file is set, that CSV is used verbatim (one "yaw_deg,pitch_deg" per
|
||||
; line, '#' comments allowed) - edit it to define exact scan coordinates.
|
||||
; If grid_file is blank, a grid is generated: yaw_intervals positions across
|
||||
; [yaw_min_deg, yaw_max_deg] at each pitch level.
|
||||
; grid_file is the ONLY source: that CSV is used verbatim (one
|
||||
; "yaw_deg,pitch_deg" per line, '#' comments allowed). Nothing is generated -
|
||||
; leave it blank and auto-sweep stays off with a message saying so, rather than
|
||||
; the tower inventing an arc nobody chose.
|
||||
; A relative path resolves against the directory of the config file it is
|
||||
; written in (config.ini sits at the repo root, so: config/scan.csv).
|
||||
; The sweep always runs front to back, then returns to the first waypoint and
|
||||
; repeats, so every pass captures the waypoints in the same order. The return
|
||||
; leg is one long slew (the yaw arc cannot wrap), so order the grid with that
|
||||
; in mind.
|
||||
grid_file =
|
||||
yaw_intervals = 56
|
||||
yaw_min_deg = -90
|
||||
yaw_max_deg = 90
|
||||
pitch_levels = 10,30,50
|
||||
grid_file = config/scan.csv
|
||||
|
||||
[Paths]
|
||||
; Directory for saved .jxl images. Supports leading ~ and $ENV expansion.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
# Staeffelsberg scan grid: 56 bearings round the full circle at one elevation,
|
||||
# matching the old firmware's 'ud56' sweep. Generated once from the retired
|
||||
# [Scan] grid generator (0 -> 353.57 deg in 56 steps, pitch 0) and kept here as
|
||||
# the explicit list, because the CSV is now the only source of waypoints.
|
||||
#
|
||||
# The sweep runs first to last, then returns to the first and repeats - always
|
||||
# the same order, never in reverse. The return leg is one long slew back across
|
||||
# the arc; that is the price of every pass being directly comparable.
|
||||
0.00,0
|
||||
6.43,0
|
||||
12.86,0
|
||||
19.29,0
|
||||
25.71,0
|
||||
32.14,0
|
||||
38.57,0
|
||||
45.00,0
|
||||
51.43,0
|
||||
57.86,0
|
||||
64.29,0
|
||||
70.71,0
|
||||
77.14,0
|
||||
83.57,0
|
||||
90.00,0
|
||||
96.43,0
|
||||
102.86,0
|
||||
109.29,0
|
||||
115.71,0
|
||||
122.14,0
|
||||
128.57,0
|
||||
135.00,0
|
||||
141.43,0
|
||||
147.86,0
|
||||
154.29,0
|
||||
160.71,0
|
||||
167.14,0
|
||||
173.57,0
|
||||
180.00,0
|
||||
186.43,0
|
||||
192.86,0
|
||||
199.28,0
|
||||
205.71,0
|
||||
212.14,0
|
||||
218.57,0
|
||||
225.00,0
|
||||
231.43,0
|
||||
237.86,0
|
||||
244.28,0
|
||||
250.71,0
|
||||
257.14,0
|
||||
263.57,0
|
||||
270.00,0
|
||||
276.43,0
|
||||
282.86,0
|
||||
289.28,0
|
||||
295.71,0
|
||||
302.14,0
|
||||
308.57,0
|
||||
315.00,0
|
||||
321.43,0
|
||||
327.86,0
|
||||
334.28,0
|
||||
340.71,0
|
||||
347.14,0
|
||||
353.57,0
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
# Scan grid — capture waypoints," one ""yaw_deg","pitch_deg"" per line."
|
||||
# Edit these to define the exact scan coordinates. Blank lines and lines,,
|
||||
# starting with '#' are ignored. The auto-sweep (ControlCode 0) walks the list,,
|
||||
# forward then backward (ping-pong), triggering the cameras at each settled,
|
||||
# point. Degrees are converted to encoder counts via the [Motor] calibration.,,
|
||||
#,,
|
||||
# Reference this file from config.ini: [Scan] grid_file = config/scan.csv,,
|
||||
#,,
|
||||
# Example: three elevation rows across a 180° yaw arc.,,
|
||||
0,10,
|
||||
45,10,
|
||||
90,10,
|
||||
135,10,
|
||||
180,10,
|
||||
0,5,
|
||||
45,5,
|
||||
90,5,
|
||||
135,5,
|
||||
180,5,
|
||||
0,15,
|
||||
45,15,
|
||||
90,15,
|
||||
135,15,
|
||||
180,15,
|
||||
# Scan grid - capture waypoints, one "yaw_deg,pitch_deg" per line.
|
||||
# Edit these to define the exact scan coordinates. Blank lines and lines
|
||||
# starting with '#' are ignored. This file is the only source of waypoints:
|
||||
# nothing is generated, so what the tower photographs is always what is written
|
||||
# here.
|
||||
#
|
||||
# The auto-sweep (ControlCode 0) walks the list from the first waypoint to the
|
||||
# last, triggering the cameras at each settled point, then returns to the first
|
||||
# and repeats - always in the same order, never in reverse. That return leg is
|
||||
# one long slew (the yaw arc cannot wrap), so order the list with it in mind.
|
||||
# Degrees are converted to encoder counts via the [Motor] calibration.
|
||||
#
|
||||
# Reference this file from config.ini: [Scan] grid_file = config/scan.csv
|
||||
#
|
||||
# Example: three elevation rows across a 180 deg yaw arc.
|
||||
0,10
|
||||
45,10
|
||||
90,10
|
||||
135,10
|
||||
180,10
|
||||
0,5
|
||||
45,5
|
||||
90,5
|
||||
135,5
|
||||
180,5
|
||||
0,15
|
||||
45,15
|
||||
90,15
|
||||
135,15
|
||||
180,15
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 1 and column 38.
|
|
|
@ -86,10 +86,7 @@ Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config
|
|||
| `Motor.<axis>` | `enc_deviation` | int | — | \|XACTUAL−XENC\| that means "position untrustworthy". **hardware.ini** |
|
||||
| `Motor.<axis>` | `enc_const` | float | 12.8 | Microsteps per encoder count (the TMC's `ENC_CONST`), up to 4 decimals. **hardware.ini**, optional: only a drivetrain whose encoder differs declares it. Wrong by a few percent, the step counter and encoder drift apart as the axis turns, and homing takes the drift for a stop (staeffelsberg: 12.5, from its old firmware) |
|
||||
| `Motor.<axis>` | `sw_mode` / `has_encoder` | int / bool | — | TMC `SW_MODE` (physical endstop switches on REFL/REFR) and whether this axis's encoder is aligned and usable. **hardware.ini** |
|
||||
| `Scan` | `grid_file` | string | — | CSV of `yaw_deg,pitch_deg` waypoints; empty → generate |
|
||||
| `Scan` | `yaw_intervals` | int | `56` | Generated yaw positions across `[yaw_min_deg, yaw_max_deg]` |
|
||||
| `Scan` | `yaw_min_deg`/`yaw_max_deg` | float | `-90`/`90` | Generated yaw arc |
|
||||
| `Scan` | `pitch_levels` | csv | `0` | Generated pitch elevations (deg) |
|
||||
| `Scan` | `grid_file` | string | — | CSV of `yaw_deg,pitch_deg` waypoints. The only source of a scan grid; empty → auto-sweep disabled |
|
||||
|
||||
### `[Motor]` calibration & `[Scan]` grid
|
||||
|
||||
|
|
@ -179,15 +176,21 @@ the tower can measure it. Yaw is untouched by it.
|
|||
#### Scan grid
|
||||
|
||||
The capture **scan grid** is the ordered `(yaw,pitch)` waypoints auto-sweep visits, always front to
|
||||
back. Set `[Scan] grid_file` to an editable CSV ([config/scan.csv](../config/scan.csv)) to define exact
|
||||
coordinates, or leave it blank to generate `yaw_intervals × pitch_levels` points.
|
||||
back, then round to the first again — never in reverse. `[Scan] grid_file` points at an editable CSV
|
||||
([config/scan.csv](../config/scan.csv)) and is the **only** source of waypoints.
|
||||
|
||||
**Nothing is generated.** Leave `grid_file` blank (or name a file that is missing, malformed or
|
||||
empty) and the program logs why and leaves auto-sweep off, showing the reason in the TUI's scan-grid
|
||||
panel. A tower that invented its own arc would archive images under bearings nobody chose, with no
|
||||
file to read back to find out what was photographed. The pre-2026-09 keys `yaw_intervals`,
|
||||
`yaw_min_deg`, `yaw_max_deg` and `pitch_levels` are gone; a config still carrying them gets a
|
||||
startup warning naming them.
|
||||
|
||||
**Grid yaw values are true bearings.** Yaw travel is under a full turn, so there is always an
|
||||
unreachable arc; waypoints falling in it are **skipped** with a warning rather than clamped to the
|
||||
nearest limit — clamping would image a direction nobody asked for and publish it under the bearing
|
||||
they did. A one-line audit after each home reports how many waypoints are affected. Generated grids
|
||||
interpolate `yaw_min_deg → yaw_max_deg` linearly and wrap each point, so an arc crossing north is
|
||||
written monotonically (`340` → `380`).
|
||||
they did. A one-line audit after each home reports how many waypoints are affected. Write an arc
|
||||
crossing north monotonically (`340` → `380`) rather than letting it wrap back through zero.
|
||||
|
||||
Each camera's output subfolder is its `[Camera.<name>] label` (defaulting to the block name); nothing is derived from the order of the blocks.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Per-file reference for the refactored tree, plus the shared data structures.
|
|||
| [include/fgc/Logger.h](../include/fgc/Logger.h), [src/core/Logger.cpp](../src/core/Logger.cpp) | Leveled, thread-safe logger + per-category wire trace; `LOG_TRACE..LOG_ERROR`, `LOG_TRACE_CAT` |
|
||||
| [include/fgc/FileLogSink.h](../include/fgc/FileLogSink.h), [src/core/FileLogSink.cpp](../src/core/FileLogSink.cpp) | The persistent log file: append, size rotation, count-based pruning, a self-describing header per file. Runs inside `Logger`'s non-recursive lock, so it never logs - failures latch into `failed()`/`lastError()` for the caller to report |
|
||||
| [include/fgc/Geometry.h](../include/fgc/Geometry.h), [src/core/Geometry.cpp](../src/core/Geometry.cpp) | Per-axis degrees↔encoder-counts affine map (`[Motor]` calibration) |
|
||||
| [include/fgc/ScanGrid.h](../include/fgc/ScanGrid.h), [src/core/ScanGrid.cpp](../src/core/ScanGrid.cpp) | Capture waypoints (CSV or generated) + one-way wrapping cursor (`[Scan]`) |
|
||||
| [include/fgc/ScanGrid.h](../include/fgc/ScanGrid.h), [src/core/ScanGrid.cpp](../src/core/ScanGrid.cpp) | Capture waypoints (CSV only, never generated) + one-way wrapping cursor (`[Scan]`) |
|
||||
| [include/fgc/TelemetryParser.h](../include/fgc/TelemetryParser.h), [src/core/TelemetryParser.cpp](../src/core/TelemetryParser.cpp) | `parseTelemetryLine` (firmware `ST` line) → `std::optional<MotorTelemetry>` |
|
||||
| [include/fgc/CommandParser.h](../include/fgc/CommandParser.h), [src/core/CommandParser.cpp](../src/core/CommandParser.cpp) | `parseCommand` whitespace tokenizer → `Command` |
|
||||
| [include/fgc/HelpText.h](../include/fgc/HelpText.h), [src/core/HelpText.cpp](../src/core/HelpText.cpp) | Operator command catalog (`helpCatalog`) + `renderHelp` (console & TUI help) |
|
||||
|
|
|
|||
|
|
@ -317,14 +317,11 @@ struct UiConfig {
|
|||
};
|
||||
|
||||
// [Scan]: source of the capture scan grid (the (yaw,pitch) waypoints the
|
||||
// auto-sweep steps through). If grid_file is set, the CSV is loaded verbatim;
|
||||
// otherwise a grid is generated from the parameters below (see ScanGrid).
|
||||
// auto-sweep steps through). The CSV is the only source - it is loaded verbatim
|
||||
// and there is no generated fallback, so what a tower photographs is always a
|
||||
// file somebody wrote and can be read back (see ScanGrid).
|
||||
struct ScanConfig {
|
||||
std::string grid_file; // path to an editable yaw_deg,pitch_deg CSV; empty => generate
|
||||
int yaw_intervals = 56; // generated: yaw positions across [yaw_min_deg, yaw_max_deg]
|
||||
double yaw_min_deg = -90.0;
|
||||
double yaw_max_deg = 90.0;
|
||||
std::string pitch_levels = "0"; // generated: comma list of pitch elevations (deg)
|
||||
std::string grid_file; // path to an editable yaw_deg,pitch_deg CSV; required for auto-sweep
|
||||
};
|
||||
|
||||
// [Test] / [TestProfile.<name>]: the hardware self-test command. A profile is a
|
||||
|
|
|
|||
|
|
@ -22,15 +22,17 @@ struct ScanPoint {
|
|||
// The cost is a long return slew from the last waypoint to the first, since the
|
||||
// yaw arc is bounded and cannot wrap. Order the grid so that leg is acceptable.
|
||||
//
|
||||
// Points come either from an operator-edited CSV (ScanConfig::grid_file) or, if
|
||||
// none is given, are generated from the [Scan] parameters.
|
||||
// Points come from an operator-edited CSV (ScanConfig::grid_file) and nowhere
|
||||
// else. There is deliberately no fallback grid: a tower that invented its own
|
||||
// waypoints would archive images under bearings nobody chose, and the operator
|
||||
// would have no file to point at to find out what was photographed.
|
||||
class ScanGrid {
|
||||
public:
|
||||
ScanGrid() = default;
|
||||
explicit ScanGrid(std::vector<ScanPoint> points) : points_(std::move(points)) {}
|
||||
|
||||
// Build from config: load grid_file if set (throws std::runtime_error on a
|
||||
// missing/!malformed file), else generate from the parameters.
|
||||
// Load grid_file. Throws std::runtime_error if it is unset, missing,
|
||||
// malformed or empty; the caller reports that and leaves auto-sweep off.
|
||||
static ScanGrid fromConfig(const ScanConfig& cfg);
|
||||
|
||||
bool empty() const { return points_.empty(); }
|
||||
|
|
@ -50,6 +52,5 @@ private:
|
|||
|
||||
// Exposed for unit testing.
|
||||
std::vector<ScanPoint> parseScanCsv(const std::string& text);
|
||||
std::vector<ScanPoint> generateScanGrid(const ScanConfig& cfg);
|
||||
|
||||
} // namespace fgc
|
||||
|
|
|
|||
|
|
@ -157,7 +157,6 @@ struct CaptureView {
|
|||
// Defined scan grid (the auto-mode waypoints) with the live cursor
|
||||
// flagged, surfaced in the expanded camera view.
|
||||
std::vector<ScanWaypointView> scan_grid;
|
||||
bool scan_from_file = false; // grid_file CSV vs generated
|
||||
bool scan_pitch = false; // 2-axis rig (show pitch column)
|
||||
std::string scan_error; // grid_file load failure (auto-sweep off)
|
||||
|
||||
|
|
|
|||
|
|
@ -925,7 +925,6 @@ struct Application::Impl {
|
|||
|
||||
// Defined scan grid + live cursor (read on the control thread, same as the
|
||||
// scheduler that mutates it, so no locking is needed).
|
||||
s.capture.scan_from_file = !cfg.scan.grid_file.empty();
|
||||
s.capture.scan_pitch = t.pitch_present;
|
||||
s.capture.scan_error = scan_load_error_;
|
||||
s.capture.scan_grid.clear();
|
||||
|
|
|
|||
|
|
@ -521,12 +521,8 @@ AppConfig ConfigLoader::fromMap(const std::map<std::string, std::string>& kv) {
|
|||
axisDrivetrain("yaw", cfg.drivetrain.yaw);
|
||||
axisDrivetrain("pitch", cfg.drivetrain.pitch);
|
||||
|
||||
// [Scan]: scan-grid source (explicit CSV file, else generated).
|
||||
cfg.scan.grid_file = get(kv, "Scan.grid_file", cfg.scan.grid_file);
|
||||
cfg.scan.yaw_intervals = getInt(kv, "Scan.yaw_intervals", cfg.scan.yaw_intervals);
|
||||
cfg.scan.yaw_min_deg = getDouble(kv, "Scan.yaw_min_deg", cfg.scan.yaw_min_deg);
|
||||
cfg.scan.yaw_max_deg = getDouble(kv, "Scan.yaw_max_deg", cfg.scan.yaw_max_deg);
|
||||
cfg.scan.pitch_levels = get(kv, "Scan.pitch_levels", cfg.scan.pitch_levels);
|
||||
// [Scan]: the scan-grid CSV. No file, no auto-sweep - nothing is generated.
|
||||
cfg.scan.grid_file = get(kv, "Scan.grid_file", cfg.scan.grid_file);
|
||||
|
||||
// ---- validation ----
|
||||
if (cfg.general.image_interval <= 0)
|
||||
|
|
@ -601,6 +597,20 @@ AppConfig ConfigLoader::fromMap(const std::map<std::string, std::string>& kv) {
|
|||
"setup'), and drivetrain keys belong under [Motor.yaw] / "
|
||||
"[Motor.pitch], not [Motor]";
|
||||
|
||||
// Keys from when a blank grid_file meant "generate a grid". The generator is
|
||||
// gone: the CSV is the only source. Ignoring these quietly would leave an
|
||||
// operator believing they had set the scan arc when nothing reads it.
|
||||
static const char* kRemovedScanKeys[] = {
|
||||
"Scan.yaw_intervals", "Scan.yaw_min_deg", "Scan.yaw_max_deg", "Scan.pitch_levels",
|
||||
};
|
||||
std::string stale_scan;
|
||||
for (const char* k : kRemovedScanKeys)
|
||||
if (kv.count(k)) stale_scan += (stale_scan.empty() ? "" : ", ") + std::string(k + 5);
|
||||
if (!stale_scan.empty())
|
||||
LOG_WARN << "[Scan] ignoring keys nothing reads: " << stale_scan
|
||||
<< " -- grids are no longer generated; list the waypoints in the "
|
||||
"grid_file CSV instead";
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,20 +16,6 @@ std::string trim(const std::string& s) {
|
|||
return s.substr(b, e - b + 1);
|
||||
}
|
||||
|
||||
std::vector<double> parseDoubleList(const std::string& csv) {
|
||||
std::vector<double> out;
|
||||
size_t start = 0;
|
||||
while (start <= csv.size()) {
|
||||
size_t comma = csv.find(',', start);
|
||||
std::string tok =
|
||||
trim(csv.substr(start, comma == std::string::npos ? std::string::npos : comma - start));
|
||||
if (!tok.empty()) out.push_back(std::stod(tok));
|
||||
if (comma == std::string::npos) break;
|
||||
start = comma + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<ScanPoint> parseScanCsv(const std::string& text) {
|
||||
|
|
@ -58,37 +44,21 @@ std::vector<ScanPoint> parseScanCsv(const std::string& text) {
|
|||
return pts;
|
||||
}
|
||||
|
||||
std::vector<ScanPoint> generateScanGrid(const ScanConfig& cfg) {
|
||||
std::vector<ScanPoint> pts;
|
||||
std::vector<double> pitches = parseDoubleList(cfg.pitch_levels);
|
||||
if (pitches.empty()) pitches.push_back(0.0);
|
||||
int n = cfg.yaw_intervals;
|
||||
if (n < 1) n = 1;
|
||||
// pitch-major, yaw inner (min -> max); the runtime cursor walks the whole list in order.
|
||||
for (double pitch : pitches) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double frac = n > 1 ? static_cast<double>(i) / (n - 1) : 0.0;
|
||||
ScanPoint p;
|
||||
p.yaw_deg = cfg.yaw_min_deg + frac * (cfg.yaw_max_deg - cfg.yaw_min_deg);
|
||||
p.pitch_deg = pitch;
|
||||
pts.push_back(p);
|
||||
}
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
ScanGrid ScanGrid::fromConfig(const ScanConfig& cfg) {
|
||||
if (!cfg.grid_file.empty()) {
|
||||
std::ifstream f(cfg.grid_file);
|
||||
if (!f) throw std::runtime_error("cannot open scan grid file: " + cfg.grid_file);
|
||||
std::stringstream buf;
|
||||
buf << f.rdbuf();
|
||||
auto pts = parseScanCsv(buf.str());
|
||||
if (pts.empty())
|
||||
throw std::runtime_error("scan grid file has no waypoints: " + cfg.grid_file);
|
||||
return ScanGrid(std::move(pts));
|
||||
}
|
||||
return ScanGrid(generateScanGrid(cfg));
|
||||
// No grid_file, no grid. Generating a default sweep here would put a tower
|
||||
// on the air photographing bearings nobody picked, under a configuration
|
||||
// that shows no waypoints at all; refusing leaves auto-sweep off and says
|
||||
// exactly what is missing.
|
||||
if (cfg.grid_file.empty())
|
||||
throw std::runtime_error("no scan grid configured: set [Scan] grid_file to a CSV of "
|
||||
"'yaw_deg,pitch_deg' waypoints (see config/scan.csv)");
|
||||
std::ifstream f(cfg.grid_file);
|
||||
if (!f) throw std::runtime_error("cannot open scan grid file: " + cfg.grid_file);
|
||||
std::stringstream buf;
|
||||
buf << f.rdbuf();
|
||||
auto pts = parseScanCsv(buf.str());
|
||||
if (pts.empty()) throw std::runtime_error("scan grid file has no waypoints: " + cfg.grid_file);
|
||||
return ScanGrid(std::move(pts));
|
||||
}
|
||||
|
||||
void ScanGrid::advance() {
|
||||
|
|
|
|||
|
|
@ -501,8 +501,7 @@ Element cameraDetailPanel(const CaptureView& c, ScrollState& st) {
|
|||
|
||||
// --- Scan grid ---
|
||||
rows.push_back(separator());
|
||||
rows.push_back(hbox({text("SCAN GRID ") | bold | color(Color::Blue),
|
||||
text(c.scan_from_file ? "(CSV file)" : "(generated)") | dim, filler(),
|
||||
rows.push_back(hbox({text("SCAN GRID ") | bold | color(Color::Blue), filler(),
|
||||
text(std::to_string(c.scan_grid.size()) + " waypoints") | dim}));
|
||||
|
||||
if (!c.scan_error.empty()) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "fgc/ScanGrid.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace fgc;
|
||||
|
||||
TEST_CASE("parseScanCsv reads waypoints and skips comments/blanks") {
|
||||
|
|
@ -17,20 +19,18 @@ TEST_CASE("parseScanCsv throws on a malformed line") {
|
|||
CHECK_THROWS(parseScanCsv("1,abc\n"));
|
||||
}
|
||||
|
||||
TEST_CASE("generateScanGrid produces yaw_intervals x pitch_levels points") {
|
||||
TEST_CASE("fromConfig refuses to invent a grid when no file is named") {
|
||||
// The generator is gone on purpose: a blank grid_file must leave auto-sweep
|
||||
// off, not produce waypoints nobody chose.
|
||||
ScanConfig c;
|
||||
c.yaw_intervals = 3;
|
||||
c.yaw_min_deg = -90;
|
||||
c.yaw_max_deg = 90;
|
||||
c.pitch_levels = "10,30";
|
||||
auto pts = generateScanGrid(c);
|
||||
REQUIRE(pts.size() == 6);
|
||||
// pitch-major, yaw inner (min -> max)
|
||||
CHECK(pts[0].yaw_deg == doctest::Approx(-90.0));
|
||||
CHECK(pts[0].pitch_deg == doctest::Approx(10.0));
|
||||
CHECK(pts[1].yaw_deg == doctest::Approx(0.0));
|
||||
CHECK(pts[2].yaw_deg == doctest::Approx(90.0));
|
||||
CHECK(pts[3].pitch_deg == doctest::Approx(30.0));
|
||||
CHECK(c.grid_file.empty());
|
||||
CHECK_THROWS_AS(ScanGrid::fromConfig(c), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_CASE("fromConfig reports a grid_file that is not there") {
|
||||
ScanConfig c;
|
||||
c.grid_file = "/nonexistent/definitely-not-a-scan-grid.csv";
|
||||
CHECK_THROWS_AS(ScanGrid::fromConfig(c), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_CASE("ScanGrid cursor always runs front to back") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue