step.motion.path.passes.absolute_heading¶
absolute_heading pass — pin each straight leg to an integrated heading.
Every straight motion (linear / follow_line) normally re-zeroes its
heading reference on whatever world heading the previous segment happened to
leave behind (executor priority #3 — current_world_heading_rad read fresh
at each boundary, see motion_factory._create_linear_motion). Across a long
path the per-leg odometry drift accumulates.
This pass instead integrates a single running heading through the node list at
compile time and stamps it onto Segment.target_heading_rad for every
straight leg, so each leg regulates against ONE coherent heading reference
rather than chaining off the previous leg’s drifted frame.
Integration convention (identical to to_absolute / segments_to_spline_waypoints):
running_heading starts at 0.0 (the path-start frame) each
turnsegment adds its signedangle_radeacharcsegment adds its signedarc_angle_radstraight legs do not change the heading
- Frame of
target_heading_rad(path-start-relative; executor applies offset): At runtime
target_heading_radis fed straight into the PID as an ABSOLUTE world heading (motion_factory._create_linear_motionline ~182:config.target_heading_rad = seg.target_heading_rad), and “world heading” is the raw, un-zeroed odometry heading (get_world_heading_rad=robot.odometry.get_pose().heading). The headings this pass emits are in the PATH-START frame (start = 0).The executor resolves the frame:
PathExecutor.runcaptures the path-start world heading H0 once at the first motion segment and adds it to every stampedtarget_heading_rad(via_with_heading_offset) before the value reachescreate_motion/ position-offset / warm-start, so each leg regulates against the correct absolute world heading regardless of the robot’s heading at path start. This pass therefore emits the relative integration only; the offset is an executor concern, now wired in.- Guards (conservative — drift correction must never fight an unknown frame):
A
SideActionresets the running heading to 0 and stops stamping until the next clean node — a side action may reorient the robot (it can drive), invalidating the integrated frame.A
None(deferred placeholder) does the same — its motion is unknown.A
Segmentwithhas_known_endpoint is False(unknown-angle turn / condition-only move) makes every downstream absolute heading unpredictable: we reset and stop stamping for the remainder of the path. (We do not try to resume after a clean run-restart heuristic — there is no clean run-restart marker in the IR, so the conservative choice is to stop until the list ends. ASideAction/Nonethat follows would reset anyway; this only differs by also stopping across a later clean stretch, which we accept rather than risk stamping a wrong frame.)An explicit user heading already on the segment (
heading_degset, ortarget_heading_radalready set) is never overwritten — user intent wins. We still advance the running heading past such legs.
arc / spline segments are passed through untouched (we never stamp a
heading onto them), but an arc with a known arc_angle_rad still advances
the running heading so following straight legs stay correct.
Classes¶
Stamp an integrated path-start heading onto every straight leg. |
Module Contents¶
- class step.motion.path.passes.absolute_heading.AbsoluteHeadingPass¶
Stamp an integrated path-start heading onto every straight leg.
Pure node→node pass. Walks the node list once, integrating a running heading (start 0; turns/arcs add their signed angle) and filling
target_heading_radon eachlinear/follow_linesegment that has no explicit user heading. Barriers (SideAction,None, or an unknown-endpoint segment) reset the frame and stop stamping; see the module docstring for the full guard rules and the absolute-vs-relative frame caveat.- name = 'absolute_heading'¶