Repository navigation
Draw the path a plane flies its mission along, and fix the arc/loiter drawing - #1753
Conversation
213c670 to
282b7cd
Compare
Previous review (2026-09-14)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Verdict: COMMENT — no blockers, two issues worth fixing. The arc and loiter fixes match ArduPilot's AP_Mission, ArduPlane and logger behaviour, and reverting each fix makes tests fail. Issues
Notes
Checked and correct: clockwise-positive arcs, endpoints and radius at high latitude and across ±180°; param4 crosstrack semantics, including NaN; the LOITER_TIME ±1 direction; "New mission"/"Mission: %u" log handling; the FTP parameter merge; 9 LogMission edge cases; no new module-scope MAVLink constants. CI: 2 passing (flake8 and the Windows build; no workflow runs pytest). |
282b7cd to
163018e
Compare
Previous review (2026-09-15)Automated review note — AI-generated (Claude), validated against the live diff (Claude + Codex cross-checked). Please sanity-check before acting. Re-reviewed at head Verdict: COMMENT, no blockers. All six findings from my previous comment are fixed. Both reviewers showed each fix is tested: undoing it makes a test fail. The new flown-path code ( Previous round
New
Checked and correct:
Tests: 169 passed in the three new files under both pymavlink 2.4.49 and master (Codex: 282 passed, 10 skipped for the whole suite). flake8 is clean. CI: 2 passing (flake8, Windows build); no workflow runs pytest. |
163018e to
918c407
Compare
Previous review (2026-09-15)Automated review note — AI-generated (Claude), independently cross-checked by a second model and re-verified against the live diff. Please sanity-check before acting. Re-reviewed at head All 8 findings from my previous comment are resolved, and 7 of the 8 fixes are shown tested by mutation. This push adds a MAVExplorer crash on older logs and a test that fails almost every run here, so the verdict is REQUEST CHANGES. Both fixes are one line. Previous round
New — please fix
Notes (non-blocking)
What was checked
REQUEST CHANGES: please fix the RALY |
918c407 to
fe8c384
Compare
Previous review (2026-09-16)Automated review note — AI-generated (Claude), independently cross-checked by a second model and re-verified against the live diff. Please sanity-check before acting. Re-reviewed at head (My previous comment above is superseded by this one. It's left un-collapsed because these reviews now post from the Every blocking finding from my previous comment is resolved, and all but one are locked in by a test that fails when the fix is reverted. Verdict REQUEST CHANGES → COMMENT. I am also withdrawing part of one of my own findings. The new items below are accuracy problems in the drawn path rather than blockers. Previous round
New — the drawn path can be silently wrong or silently absent
Adjusted from the cross-check
Notes (non-blocking)
What was checked
Not reviewed: the ported COMMENT — nothing blocks merge. The turn-count cap and the silent fallback are the pair I'd most want fixed, since together they mean a legitimate mission loses its flown path with no explanation. |
Previous review (2026-09-16)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head Full report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_17_0037/devcall_pr_reviews.html#prMAVProxy-1753 Verdict: COMMENT (was REQUEST CHANGES) — twelve of fifteen previous findings are fixed, and 24 of 26 mutations I tried are caught by the new tests, each with a named failing test. Nothing here blocks merge. Two new accuracy findings, one of which corrects something I got wrong last round. Previous roundResolved: the Still open, all re-raised rather than new: CI runs no tests at all (repo-level, not yours — with wx blocked 88 tests skip against 13 normally); terrain that never resolves leaves the path silently at the aircraft's altitude ( ISSUE —
|
| distance from home | ArduPilot target | model draws | error |
|---|---|---|---|
| 1200 m | 100.00 m | 50.23 m | −49.8 |
| 990.9 m | 100.00 m | 44.73 m | −55.3 |
| 807 m | 100.00 m | 39.79 m | −60.2 |
| 501 m | 61.50 m | 31.54 m | −30.0 |
| 195 m | 19.77 m | 23.31 m | +3.5 |
(rtl_alt_delta=85, sink_dist=623.3 m, so the real ramp reaches RTL_ALTITUDE at 783.3 m.) Not version-sensitive — the ramp has been there since 3bb840f794 (2021-06-01) and is still on master. Only Q_RTL_MODE=3 is affected: mode 1 draws 100 m all the way, which matches RTL, and mode 2 goes through fly_vtol_approach. Accuracy only; the horizontal path is unaffected. The comment at :1035-1036 is right about the destination and silent about the approach.
Your own fixture cannot see it either: sitl-quadplane-rtl-q-rtl-mode-3.json was recorded with RTL_ALTITUDE=20, Q_RTL_ALT=15, so rtl_alt_delta=5 m and the ramp is flat there by construction.
ISSUE — tests/test_plane_track.py:1017 — the SITL comparison is horizontal only
distances() projects to (north, east) and drops the third element; arrivals() uses gps_distance. Making every drawn point 200 m too high leaves all 20 TestFlownMission assertions green (20 passed in 1.78s). The recordings already carry altitude (record_flight.py:141 writes round(m.Alt, 1)), so a vertical assertion is nearly free — and it is the highest-value thing you could add, since this PR moves a lot of altitude logic and none of it is checked against a real flight. On the mode-3 recording drawn is 4–7 m high throughout (at 990 m: flown 70.1, drawn 74.2) and nothing looks.
ISSUE — tests/missions/*.json — the fixtures cite commits and autotests that do not exist publicly
gh api repos/ArduPilot/ardupilot/commits/cd2319308b → 422 "No commit found"; same for f2c18f532f, and same against peterbarker/ardupilot. Plane.MissionItemTypes, QuadPlane.VTOLMissionItemTypes and QuadPlane.KalaupapaCanyonRun are in neither arduplane.py nor quadplane.py on master, GitHub code search returns 0 for all three, and no open ArduPilot PR mentions them. So the ground truth for the headline claim of 3e5a9b70 cannot be regenerated by anyone else, and "ArduPilot's autotest now has flights meant for this" is not true upstream yet. Either land the ArduPilot side first and cite resolvable SHAs, or say in the commit and in source that these came from a working branch. (Still better than the old canyon fixture, which cited no SHA at all.)
Notes
map3d/__init__.py:909— a non-finiteWIND.directionraises out ofmavlink_packet:nan→ValueError,inf→OverflowError(driven through the real handler). ArduPilot sendswrap_360(degrees(atan2f(...))), NaN if the EKF wind estimate is NaN.mavproxy_link.py:1139catches it, so at defaultmoddebug 0the message is silently dropped. One-linemath.isfiniteguard.tests/test_map3d_mission_labels.py:204— importsmap3d_ui(→wx) at line 204, before the fixture'spytest.importorskip("wx")at 205, so it fails rather than skips without wxPython. New ince2179a5; it is the only one of your tests that fails in that configuration.terrain.py:89— the new whole-fetch deadline cannot interrupt a slow buffered read, becauseresponse.read(64*1024)blocks until its buffer or EOF and eachrecvresets the socket timeout. Measured:timeout=0.1with a 4096-byte body dripped at 64 B/50 ms → 3.163 s;timeout=5.0with 65536 bytes at 2048 B/400 ms → 12.410 s. This is still an improvement on master, which had no whole-fetch deadline at all — the guard is just partial. One caller, already wrapped intry/except, so it is a nit. (Also:94-95uses%.0f, sotimeout=0.1prints "took longer than 0s".)record_flight.py:117—if value:drops a legitimately-zeroheading/approach; a due-north approach would be recorded with noapproachand replayed atSTILL_AIR_APPROACH, a 180° error.if value is not Noneis the intent.test_plane_track.py:1068—near = abs(params[1]) * 1.4is 0 for aLOITER_TO_ALTwith param2 0 (meaningWP_LOITER_RAD). Latent; no current fixture has one.
Suite at this head: 414 passed, 13 skipped, 20 subtests on Python 3.14.4 / pymavlink 2.4.49; five full runs identical under randomised ordering; the six timing-sensitive TestDrawnTrack tests 25/25 in isolation. 11 module-scope mavutil.mavlink.* references enumerated by AST across the 13 changed files, 0 missing under released 2.4.49.
Previous review (2026-09-18)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head Full report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_18_1127/devcall_pr_reviews.html#prMAVProxy-1753 Both accuracy ISSUEs from my previous comment are properly fixed, and I locked each one in with the exact mutation that exposed it last round. Four new commits, all of them responding to that comment. Verdict: COMMENT — everything new this round is a NOTE. Resolved since last round
Still open and re-raised rather than new, all outside this PR: CI runs no tests at all ( New — all NOTEsN1 — The gradual descent from the approach-entry altitude down to Measured with the new test's own parameters (entered at 300 m,
Below I don't think this blocks anything: tracking master is a defensible call, and the previous state (an ordinary glide slope) was wrong for both. But MAVProxy is mostly pointed at released firmware and the divergence is ~100 m. One line in the N2 — N3 — N4 — N5 — Checked cleanThe new Reviewed by: Claude + Codex (cross-checked) — a full read of the delta, the thread and the surrounding source, 12 mutations against the full suite, real-socket timing measurements and a line-by-line comparison against ArduPilot master and (My previous AI comment could not be marked deprecated in place — it was posted under a different account and the edit is refused. It is superseded by this one.) |
Previous review (2026-09-18)Automated review note — AI-generated (Claude), cross-checked by an independent Codex pass against the live diff. Please sanity-check before acting. Re-reviewed at head Full report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_18_1407/devcall_pr_reviews.html#prMAVProxy-1753 All three open notes from my last comment are closed, and the two that needed more than a docstring were verified by execution. Nothing new from either pass. Verdict: APPROVE. Resolved since last round
Closed, no change neededN4 ( Still open, all pre-existing and none introduced hereCI runs no tests at all — no workflow invokes pytest, so the 429 tests in this PR are never executed by CI. That remains the single highest-value thing that could change about this repository, and it is not this PR's job. Also What I checked rather than assumedThe delta is exactly two commits, appended rather than force-pushed ( Reviewed by: Claude + Codex (cross-checked). |
8a70115 to
0c947c8
Compare
Previous review (2026-09-28)Automated review note — AI-generated (Claude), validated against the live diff (Claude + Codex cross-checked). Please sanity-check before acting. Re-reviewed at head Everything from my previous comment still holds, and master fixed the biggest caveat: CI now runs pytest. That also turns this PR red, which is the one thing that has to change before merge (a one-line fix, item 1), so the verdict moves from ACCEPT to REQUEST CHANGES. Items 2 and 3 are real fidelity problems in the new code and worth fixing too. This round adds 4 commits: mission-end RTL, takeoff pitch/climb, reading tlogs in MAVExplorer, and a takeoff drawn towards the next waypoint. The rebase conflicts in
New
Checked and correct
|
Previous review (2026-09-28)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head 4 of the 5 items from my previous comment are resolved. The 5th, the tlog mission handling, is mostly resolved: one part still doesn't work on real logs (below). The CI blocker is fixed and
The move itself is clean. Still openA re-downloaded mission only counts as "the same" if it is bit-identical:
Leaving seq 0 out of the comparison, and comparing positions with a small tolerance, fixes all of these. I tried a quick version and all 168 tests still pass. Minor
|
Previous review (2026-09-28)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head All but one item from my previous comment is resolved, and each fix is pinned: reverting it makes a named test fail. The mid-flight re-download is fixed for the cases I listed, but it still resets on ordinary missions. My suggestion last round didn't go far enough there. You did what I asked. CI is green (
The 9 fixture JSON edits only drop the course the takeoff doesn't use. Still open:
|
901e73b to
683f777
Compare
Previous review (2026-09-29)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at A few smaller things, none blocking:
|
Previous review (2026-09-30)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at A few smaller things, none blocking:
|
Previous review (2026-10-01)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at Three smaller things, none blocking:
|
Previous review (2026-10-01)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at Two smaller things, neither blocking:
|
Previous review (2026-10-01)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at One small thing, not blocking:
Not exercised: SITL, real logs opened in the GUI, and pymavlink other than 2.4.49. |
Previous review (2026-10-02)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at A few small things, none blocking:
Not exercised: SITL, real logs in the GUI, the VTK view, and pymavlink other than 2.4.49. |
fe11a40 to
c0fa3f1
Compare
Previous review (2026-10-03)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at Still open from last time:
New:
Not exercised: SITL or a real vehicle, real logs opened in MAVExplorer, the wx/VTK GUI (three display tests skipped), real terrain fetching, and pymavlink other than 2.4.49. CI is 'passing' at |
c0fa3f1 to
c20f211
Compare
Previous review (2026-10-07)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at
Each fix has a passing test (343 passed headless). None of the points below is blocking. All of them affect only the drawn prediction.
Not exercised: SITL or a real vehicle, real logs in the GUI, real terrain fetching, and pymavlink other than 2.4.49. CI is pending at |
c20f211 to
2281c8d
Compare
Previous review (2026-10-08)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at
Not exercised: SITL or a real vehicle, real logs in the GUI, real terrain fetching, and pymavlink other than 2.4.49. CI is pending at |
pymavlink 2.4.49, the latest release, has no MAV_CMD_NAV_ARC_WAYPOINT or MAV_CMD_DO_ORBIT in any of its dialects, while setup.py asks only for pymavlink>=2.4.38. The 2D map module, its mission arcs, the 3D map and mission_circle_radius() all look them up in the dialect, so with a released pymavlink they raise AttributeError: drawing a mission with a loiter in it fails, and 21 of the tests do too. Name the two commands in mp_util, and use those. The arc waypoint was given a fallback of its own in each of the three modules which draw it; there is one name for it now, and DO_ORBIT, which is still looked up in the dialect, has one too.
Everything the 3D map draws -- terrain, the flight path, the mission, fences, rally points and KML -- goes through enu() into a local frame about the view's origin, which subtracted longitudes as they are. Either side of the antimeridian was then drawn the world apart: with the origin at 179.998, a point at -179.999 a few hundred metres east landed nearly 40,000km west. Wrap the difference, as ArduPilot's diff_longitude() does. A point given past 180 instead of wrapped, as ring_points() gives them, still lands where it did. The rest of the map made the same mistake. The camera's focal point went past 180 as it moved east over the antimeridian, picking terrain tiles from the western edge of the world rather than the ones beside it, and tiles were fetched by how far they were from the camera in unwrapped longitude, so those just across were fetched last. Terrain heights under a point past 180 were looked for in no tile at all, terrain samples fetched the wrong tile, looking at a point across the antimeridian turned the camera the world away, and a fence edge across it was sampled for terrain the long way round. MAVExplorer centred its view on the average longitude of the flight path, which for a flight across the antimeridian is on the far side of the world.
"map: plot the loiter path a vehicle actually flies" works out how many turns a NAV_LOITER_TO_ALT is drawn with from how much climb is left when the vehicle arrives. The live module measured that by subtracting the raw altitude of the item before from the loiter's own, whatever frames the two were in, so an AMSL item before a home-relative loiter read as a dive of the whole height of the field, and the spiral was drawn with the turns of that dive. Resolve both to AMSL before subtracting. An altitude that cannot be resolved yet is unknown rather than guessed: a relative item before home is known, or a terrain-frame item before the terrain under it has been fetched, has nothing to measure against, and its loiter is drawn as a single turn. MAVExplorer already resolved its items before measuring, so it was not affected. The test goes through send_mission() itself rather than only the helper, and fails against the raw subtraction.
"map3d: join and leave loiter circles on a tangent" draws the vehicle pulling back onto a track out of a loiter's centre whenever a circling item's param4 was not positive. That is what param4 means for three of the circling commands and nothing like it for the other two: LOITER_UNLIM's param4 is a yaw angle, and it is never left anyway, and DO_ORBIT counts its turns there. ArduPilot agrees: AP_Mission turns param4 into loiter_xtrack for LOITER_TURNS, LOITER_TIME and LOITER_TO_ALT and for no other command, and ArduPlane only restarts the next leg from the exit point when that flag is set. mission_crosstracks_from_centre() says the same, and both the live map and MAVExplorer ask it rather than testing param4 themselves; each has a test which fails if it goes back to reading param4 directly.
"map: plot loiter and orbit commands using their radius" added mission_circle_turns() to read the turn count a LOITER_TURNS or a finite DO_ORBIT carries, but nothing called it: both producers only ever filled circle_turns in for NAV_LOITER_TO_ALT, which works its own count out from the climb. A three-turn loiter climbing while it circled was drawn as a single turn. Hand the count through from the live module and from MAVExplorer, with LOITER_TO_ALT still working its own out. A circling item at a level altitude is still drawn as one turn whatever it asks for, since the rest would be drawn exactly on top of the first. The drawing tests build their MissionItems by hand, which is how this went unnoticed, so the new tests go through the producers instead: the live module's send_mission() and MAVExplorer's resolve_mission_amsl().
The MAVLink spec gives NAV_LOITER_TIME a radius in param3, but ArduPilot has nowhere to keep one: the loiter time takes all sixteen bits of the only spare field a location-carrying command has, so AP_Mission keeps param3 as the loiter_ccw bit alone and hands it back as -1 when the item asked for counter-clockwise and +1 otherwise. ArduPlane's verify_loiter_time() calls update_loiter(0) whatever the item was uploaded with, which flies WP_LOITER_RAD: counter-clockwise if the item asked for it, and otherwise whichever way WP_LOITER_RAD's own sign says. mission_circle_radius() read the +-1 as a radius, so a LOITER_TIME from a live vehicle or a log was drawn as a circle a metre across -- nothing anyone can see -- and one from a mission file, or read from an upload in a tlog, at a radius the vehicle never flies. Take the size from the vehicle's own radius for every LOITER_TIME, and follow its direction too: a negative param3 turns the circle counter-clockwise, and anything else leaves it going the vehicle's way. With no vehicle radius to take, nothing is drawn. update_loiter() treats a radius of a metre or less on the other loiter items the same way, and flies LOITER_RADIUS_DEFAULT, 60m, when WP_LOITER_RAD is itself a metre or less. Do both of those as well, rather than drawing a circle a metre across or none at all. Found flying a plane mission in SITL: the vehicle handed back p3=1.0 for a LOITER_TIME uploaded with none.
cmd_map3d appended every CMD message it found to the mission it drew. A log carries the whole mission once at its start and again every time one is uploaded, so a flight which had its mission changed drew every mission it had ever held, joined into one line by the legs between the end of one and the start of the next. Keep the items by sequence number instead, and start again at the logger's "New mission", which it writes before every mission it logs -- including a cleared one, which has no items after it at all. A shorter mission replacing a longer one therefore leaves nothing of the longer behind, and a cleared mission draws nothing. Logs from before the logger wrote the message still start again at a mission's first item. The reading moves out of cmd_map3d into mission_from_log(), so the tests can hand it a log. Found in a SITL plane log where the vehicle booted holding one mission and was then sent another: MAVExplorer drew 31 items, both missions end to end, for a flight of 16.
A takeoff item normally carries an altitude and no position: the vehicle climbs from wherever it is when the item runs. The live 3D map draws one as the climb from home, but cmd_map3d dropped every CMD without a position, so from a log a mission started at its first waypoint and the line ran diagonally up to it instead of showing the climb. A log can say where the climb really began. The autopilot announces "Mission: <seq> <command>" as it starts each item, so the takeoff is drawn from where the vehicle was at that moment. A mission never flown far enough to get there falls back on home, the mission's own first item, which ArduPilot sets where it arms; and a mission uploaded before the vehicle had a home leaves that item empty too -- as happens in SITL -- so where the flight itself began stands in for it then. Anything else without a position still has nowhere to be drawn and is left out. TAKEOFF_COMMANDS moves to mp_util, so the module and MAVExplorer read it from the one place. Found flying a copter mission with a positionless takeoff in SITL.
The 2D map's loitercircle and showdirection settings were both off, so a mission with loiters in it was drawn as a line through their centres with nothing to say the vehicle circles there, and nothing to say which way round the mission runs. mavflightview and MAVExplorer draw the circles whatever the settings say, so the same mission looked different depending on which one it was opened in. Now that the circles are the ones the vehicle flies, turn both on, and turn on the 3D map's direction arrows in the map3d module and MAVExplorer with them, so the live maps and MAVExplorer's 3D map show a mission's circles and its direction alike. mavflightview has no direction setting and still draws its mission without arrows. "map set loitercircle 0", "map set showdirection 0" and their map3d and MAVExplorer equivalents put them back.
plane-mission-geometry.txt and copter-mission-geometry.txt are SITL-flyable missions which exercise the drawing this fixes: climbing and descending spirals, crossing and non-crossing tangents between loiters, both param4 settings, mixed altitude frames, the arc waypoints only a multirotor flies, and the loiters it holds position for. README.md describes each one and what to look for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A telemetry log carries the missions downloaded from the vehicle or uploaded to it, each a MISSION_COUNT and then its items, in whatever order they were asked for and perhaps never all of them; a dataflash log carries a CMD for each item whenever the vehicle writes the whole mission out. LogMission, in the lib so that any of the tools can read a log's mission with it, reads either and keeps only the last mission to arrive whole: the map draws the mission the vehicle was flying rather than every one the log holds joined end to end, a transfer the log ends part way through leaves the one before it as it was, and a mission cleared away is cleared. MISSION_CLEAR_ALL, the float items a ground station may upload instead of MISSION_ITEM_INT, and the frame a dataflash CMD logs are taken with it. WP_LOITER_RAD, which the loiter circles are drawn at, is looked up once the log has been read, since a telemetry log carries the parameters as it goes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When MAVProxy fetches a vehicle's parameters over FTP it logs them to the tlog itself, as PARAM_VALUEs from MAV_COMP_ID_MISSIONPLANNER so they can be told apart from ones the autopilot sent. A log's params only offer the autopilot's own, so a log from a vehicle whose parameters were fetched that way has just the few it sent as they changed -- 35 of 1479 in the log this was found in, and not WP_LOITER_RAD, so mavflightview still drew no loiter circles. Add log_params(), which takes both, the autopilot's own values over the fetched ones, and use it where mavflightview and MAVExplorer's 3D map read parameters to draw a mission. A log picks the vehicle whose parameters it offers from its first heartbeat, and one with no heartbeat in it -- cut out of a longer log, say -- offers none at all. Take the parameters of the one vehicle they came from there, and none where several vehicles sent some, rather than guess between them.
A mission uploaded before the vehicle has a home logs its home item as nothing at all, and resolve_mission_amsl() then measures every home-relative altitude from the lowest position in the log instead. The first positions are logged before the EKF has an origin, at nothing too, so a QuadPlane log from SITL drew its relative-altitude items 165m low. The vehicle logs its home in ORGN once it has one. Where the mission's home item is empty, fill it from the last one logged.
Work out the path a plane or QuadPlane flies a mission along, rather than the lines between its items: a port of enough of ArduPlane's navigation to fly the mission with its L1 controller, at the aircraft's own airspeeds, roll limits and climb rates. It flies every loiter, with the radius and direction the item and WP_LOITER_RAD give it and the exit ArduPlane takes -- lined up on the next item as the mission stores it, or crosstracking from where the circle is left; waypoints with their acceptance and pass-by radii, and WP_MAX_RADIUS; a fixed-wing takeoff at the item's pitch, or faster where TECS has an airspeed sensor to fly by, and a VTOL one straight up and away the way the aircraft points; the return to launch a RETURN_TO_LAUNCH item or a mission running out makes, to home or the rally point nearest it, with the circle a plane ends in and each Q_RTL_MODE a QuadPlane lands by; a VTOL landing straight in or on the fixed-wing spiral approach param1 or Q_OPTIONS asks for, into the wind; and jumps, jump tags, speed changes and altitude frames along the way. Where the path cannot be worked out -- a command it does not know, an item the aircraft never finishes, or a flight longer than it is worth following -- it says so, and the caller draws the mission's geometry instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 3D map draws the path the vehicle is going to fly its mission along, worked out by plane_track, rather than the lines between its items. It is flown on a thread of its own, since flying a long mission takes a moment, and drawn as soon as it is worked out; a mission which cannot be flown through is drawn as its geometry, and the map says why once. The mission is flown again when anything it is flown by changes: the mission itself, home moving along or up and down, the wind a VTOL landing approaches into, the way the vehicle points before a takeoff, or any of the parameters. A download still coming in is drawn but not flown, since the items so far would be flown as a mission which runs out. A rally point a return would go to is drawn where the return goes to it, at the altitude its own frame gives it. The view has controls of its own for what to draw: the mission as the path flown, as its geometry, or as plain lines between items, and whether to label each item with its number and kind, at a size the labelsize setting gives. Those and the module's settings end up agreeing whichever way a change crosses between them, and a view opened later is told what they are. The labels are the 2D map's, which both now take from mp_util. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MAVExplorer reads the mission a log holds with LogMission, so a dataflash log and a telemetry log are read the same way, and draws the path the vehicle flies it along in the 3D map, with the rally points a return may go to and home as the log gives them. What the log says of the flight is used where the mission does not say: where the mission's first item began, which is where the flight starts, and the course measured over the start of the takeoff, which is the way a QuadPlane pointed as it transitioned and the course a fixed-wing takeoff held -- nothing before a launch says either. The course a VTOL landing approach was flown on is the one the vehicle announced. A mission downloaded again mid-flight is recognised as the one being flown, compared as AP_Mission gives it back and leaving out home, which is the vehicle's own: so where the flight had reached in it survives a ground station asking the vehicle what it holds. What the log holds only part of -- a mission or a rally table a transfer ends part way through -- is drawn, since those items are where the vehicle was told to go, but not flown, since what would have followed them is not known. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wp ftp, wp ftpload and wp show fill the loader without saying how many items to expect, so what they load looks like part of a download which stopped: the wp module asks the vehicle for items it already has, and anything else watching the loader waits for the rest of a mission which is all there. Say the count, as loading from a file already does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ten SITL flights of ArduPilot's autotest missions are recorded here, and the path drawn for each mission is held to the one flown: across the ground, and in height. The flights are of the missions ArduPilot PR 34424 adds -- QuadPlane.KalaupapaCanyonRun, Plane.MissionItemTypes and QuadPlane.VTOLMissionItemTypes -- which between them fly every kind of item ArduPlane flies, every way a mission ends, every Q_RTL_MODE and each way a QuadPlane lands. Each flight is checked three ways: that every item is reached in the order the mission takes them, with about as far flown between each pair as the aircraft flew; that each point flown is near the path drawn, within limits of its own; and that it is at the height drawn, measured to the nearest point of the path so that a spiral is measured against the turn of it the aircraft was on. Half of each flight is drawn within 0.2 to 9m of where it went, and nine tenths within 4 to 28m. A recording says which firmware flew it, since the drawing follows what a version of ArduPlane does, and record_flight.py makes another from a log: what MAVExplorer hands the drawing, and the path flown once a second from the end of a QuadPlane's transition or a plane's takeoff to the start of its landing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two places the mission was read differently from AP_Mission: - Meeting a DO_JUMP twice over ended the drawn mission. AP_Mission gives up that way only while it is looking ahead (AP_Mission::get_next_cmd); advancing, it runs the jump's repeats out and carries on past it, so a loop with nothing to navigate to in it -- a jump to itself, or one over commands which are not flown to -- is flown past rather than ending the mission 3km short of the item after it. - Any negative repeat count was taken as repeating for ever, where AP_MISSION_JUMP_REPEAT_FOREVER is -1 alone and a jump asking for fewer is never followed at all. An item with no position of its own and no altitude either is flown at the altitude the aircraft is at, which Plane::set_next_WP() gives it in whatever frame the zero is in; an absolute zero was drawn as 0m AMSL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 3D map read them from a rallyloader on the rally module, which has no such thing: it is a MissionItemProtocolModule, and answers for its points itself. So nothing was ever drawn for them, and a return to launch in the live map went to home however near a rally point was. The tests had a double with the attribute the code reached for, which is why they did not notice; they now stand in for what the module really offers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rames Two things about the path the live map draws: - An item above terrain which has not been fetched yet was flown at whatever height the aircraft happened to be at, 390m out where the real ground was. Stand it above home until the terrain arrives, which is near enough to be worth drawing, and the mission is drawn again as soon as the terrain is known. - The path already drawn is kept against what the mission is, which folded an item's altitude frame away: 100m above a home at 584m and 100m AMSL were the same item, so changing one to the other drew the path worked out for the other. Keep the frame with the altitude. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A rally table is only a table to work a return out from when the whole of it is known, and two ways of knowing it were missed: the logger writing out a table with nothing in it -- 'New rally' and no points -- which is the whole of it, and a download which says how many points to expect and ends before any of them arrive, which is none of it. The first left a mission unflown for want of points which had been cleared away, and the second flew it home as though the vehicle held none. The settings an open 3D view is drawn by are also taken each time round the loop rather than only when a set command gives them, so changing the mission path, the labels or their size in the settings dialog reaches the view: choosing 'flown' there drew nothing until some later set command. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2281c8d to
6b784a1
Compare
|
Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at The six points from last time are all still open:
Two new minor points:
Not exercised: SITL or a real vehicle, real logs in the GUI, real terrain fetching, and pymavlink other than 2.4.49. CI is pending at |
Follow-up to the arc and loiter mission drawing already on
master("map: plotnew arc waypoints better" through "map3d: show which way the mission is flown").
It started as fixes found by review and by flying test missions in SITL, and
grew into drawing the path a plane is actually going to fly, checked against
SITL flights of ArduPilot's own autotest missions.
The path a plane flies its mission along
MAVProxy/modules/lib/plane_track.pyis new: a port of enough of ArduPlane'snavigation to work out where a plane or QuadPlane will go, rather than drawing
straight lines between items. It flies the mission with ArduPlane's L1
controller, at the aircraft's own speeds, roll limits and climb rates, and
models:
as the mission stores it, or crosstracked from where it leaves the circle);
WP_MAX_RADIUS;airspeed sensor to fly by, on the course a log shows it was flown on; a VTOL
one straight up, transitioning the way the aircraft points;
ends in, and each
Q_RTL_MODE: QRTL's own altitude profile, and thefixed-wing spiral approach a QuadPlane lands from, flown into the wind;
param1orQ_OPTIONSasksfor;
ArduPlane flies when a mission runs out.
Where it cannot work a path out -- a vehicle that is not a plane, a command it
does not know, a mission that never finishes an item, or one the log holds only
part of -- it says so and the map falls back to drawing the mission's geometry.
Where it is drawn
flown,geometryorplain, a Labels checkbox and alabelsizesetting.The view's controls and the module's settings now agree whichever way a change
crosses. The path is worked out on a thread, and redrawn when the mission,
home, the wind, the way the vehicle points, or any parameter it is flown by
changes.
map3dnow works from telemetry logs aswell as dataflash ones.
Reading a mission out of a log
LogMissionmoves frommavflightviewtoMAVProxy/modules/lib/log_mission.pyand is what both it and MAVExplorer read a mission with, so a log's mission is
read one way:
the log ends part way through no longer replaces a complete mission;
MISSION_CLEAR_ALL, floatMISSION_ITEMuploads and aCMD's logged frameare all honoured;
compared as AP_Mission gives it back, and ignoring home, which is the
vehicle's own -- so where the flight had reached in it survives;
the rally table alike: the items that arrived are where the vehicle was told
to go, but what would have followed them is not known.
Drawing fixes
LOITER_TO_ALTread as a huge dive and drew the wrong number of turns.LOITER_TURNS,LOITER_TIMEandLOITER_TO_ALT.LOITER_TIMEradius of +-1 is a direction, and drew a 1 m circle instead ofWP_LOITER_RAD.Behaviour change
map: draw loiter circles and direction arrows by default. The 2D map's
loitercircleandshowdirectionare on, as isshowdirectionin map3d andMAVExplorer, so every view of a mission looks the same.
Tests
The suite goes from 261 to 595 tests (5 skipped without wx or VTK); flake8 is
clean.
tests/test_mission_geometry.pygrows from 50 to 86 cases, andtests/test_plane_track.pyis new with 158.Ten SITL flights are recorded under
tests/missions/and the drawn path is heldto each one, both across the ground and in height. The flights come from
autotests proposed in ArduPilot PR 34424 (
QuadPlane.KalaupapaCanyonRun,Plane.MissionItemTypes,QuadPlane.VTOLMissionItemTypes), which between themfly every kind of item ArduPlane flies, every way a mission ends, and every
Q_RTL_MODE. Each recording says which ArduPlane flew it, since the drawingfollows a version of ArduPlane's behaviour, and
tests/missions/record_flight.pymakes another from a log.
The worst figures are mostly a QuadPlane's transition and a plane's run down the
runway, neither of which is modelled; after the first 30 s each flight stays
closer.
tests/missions/plane-mission-geometry.txtandcopter-mission-geometry.txtare SITL-flyable missions that exercise thegeometry drawing, described in
tests/missions/README.md.Every fix in the branch was checked by reverting it and watching a named test
fail, and the review rounds on this PR were answered the same way.
Known limits
straight to
RTL_ALTITUDE, so a return entered high is flown lower than it isdrawn there; the docstring says so.
LOITER_TIME'sdwell is not turned into turns; extra laps would sit on top of the first.
TECS_CLMB_MAX/TECS_SINK_MIN, which SITLflies a little slower and steeper than.
(pre-existing).
needs that branch until it lands.