step.motion.path.passes.to_absolute¶
to_absolute pass — turn dead-reckoning relative runs into ONE navigate-to-pose.
A contiguous run of consecutive known-endpoint linear / turn /
diagonal segments (no live condition) is converted into a SINGLE
GotoWaypoints navigate-to-pose
move. GotoWaypoints captures the localization pose ONCE at the run start
(its on_start) as a single anchor, precomputes the ABSOLUTE world target for
each of the run’s waypoints (fixed geometry, known at compile time), then
regulates onto each target in sequence using the particle filter as feedback —
so the open-loop dead-reckoning legs become one closed-loop run that shrugs off
odometry drift. Anchor scope is PER RUN: each contiguous run captures its own
anchor at its first leg, and every waypoint within the run is relative to that
ONE anchor (not chained leg-to-leg), so drift does NOT accumulate across legs.
Crucially this requires ZERO executor changes: the replacement node is an inline
(blocking) SideAction, and the executor already runs inline side actions via
await step.run_step(robot). GotoWaypoints is a MotionStep, whose
run_step drives its full on_start / on_update / on_stop loop to
completion — so the run drives its closed-loop controller through every target.
Run-detection: a MAXIMAL run is a stretch of consecutive Segment nodes that
are ALL kind in ("linear", "turn", "diagonal"), has_known_endpoint is
True and whose
condition is “baked” — i.e. None, or a bare relative after_cm whose
distance is folded into distance_m at lowering time (the runtime odometer
stop is kept but the geometric endpoint is exact). Anything else — a SideAction, a
None deferred placeholder, an arc / follow_line / spline
segment, a segment with a live (early-stopping / sensor / absolute) condition or
an unknown endpoint — BREAKS the run and passes through unchanged. Body-frame pose is integrated from (x=0, y=0, heading=0) at the
run start, reusing the SAME forward/left integration as
segments_to_spline_waypoints (in spline.py). ONE waypoint is
collected per LINEAR segment endpoint (preserving the path shape — no corner
cutting); turns between linears fold into the following waypoint’s target
heading. All the run’s waypoints feed ONE emitted GotoWaypoints.
SENSOR-bounded single-axis linear legs. A linear segment with NO known
endpoint and a real (non-baked) condition — e.g. strafe_left().until(
on_black) — does NOT join a known-endpoint run, but it pins 2 of 3 world DOF
(the cross-axis position + heading) and leaves only the travel axis free until
the sensor fires. Such a leg is converted into a single inline
SideAction(AbsoluteHoldMove): a closed-loop-on-localization step that holds
the 2 pinned DOF on their absolute targets (correcting drift DURING the move,
read-only on the particle filter) while driving the free axis open-loop until
the original .until() condition fires. This keeps the WHOLE to_absolute
path on the particle filter rather than odometry dead reckoning. Each
AbsoluteHoldMove re-anchors at its own on_start, so the next known run
re-anchors naturally and a sensor leg flushes any in-progress known run.
Best-effort: segments that don’t qualify are left untouched; the pass never
crashes. .until(after_cm) legs qualify automatically — their distance is
recovered at lowering time, so a BAKED after_cm leg becomes a
GotoWaypoints waypoint, NOT an AbsoluteHoldMove. follow_line /
spline / arc / diagonal sensor legs are left untouched.
to_absolute is driven by the builder as a MODE flag (optimizer._absolute),
not a representation-changing pass. When splinify() is also chained the whole
path becomes ONE absolute spline and this per-leg pass is skipped; with only
to_absolute() the builder appends this pass for the per-leg conversion. It
leaves produces at the default (SAME) so chaining with the terminal
splinify() is allowed.
Attributes¶
One GotoWaypoints leg, fully resolved at COMPILE time: |
Classes¶
Convert each known-endpoint relative run into ONE closed-loop |
Module Contents¶
- step.motion.path.passes.to_absolute.Waypoint¶
One GotoWaypoints leg, fully resolved at COMPILE time:
(rel_forward_m, rel_left_m, abs_dx_m, abs_dy_m, heading_kind, heading_value).GotoWaypoints composes it against the single run anchor as:
world = anchor.pos + Rot(anchor.heading)·(rel_forward, rel_left) + (abs_dx, abs_dy) theta = anchor.heading + heading_value if heading_kind == "rel" heading_value if heading_kind == "abs"
- class step.motion.path.passes.to_absolute.ToAbsolutePass¶
Convert each known-endpoint relative run into ONE closed-loop
GotoWaypoints.Pure node→node pass. Replaces each maximal run of qualifying
linear/turnsegments with a SINGLE inlineSideAction(GotoWaypoints)node carrying all the run’s body-frame waypoints — one per linear endpoint. TheGotoWaypointscaptures one anchor per run at start and drives to the resulting ABSOLUTE world targets in sequence (so drift does not chain leg-to-leg).Additionally converts each SENSOR-bounded single-axis
linearleg (no known endpoint, real non-baked condition — e.g.strafe_left().until( on_black)) into a single inlineSideAction(AbsoluteHoldMove)that holds the cross-axis position + heading absolutely (2 DOF, read-only on localization) while driving the free axis until the sensor fires. Such a leg flushes any in-progress known run first.Non-qualifying nodes pass through unchanged.
.until(after_cm())legs qualify automatically — their distance is recovered at lowering time, so a bakedafter_cmleg stays aGotoWaypointswaypoint (NOT anAbsoluteHoldMove).follow_line/spline/arc/diagonalsensor legs pass through untouched.to_absolute is driven by the builder as a MODE flag (
optimizer._absolute), not a representation-changing pass: whensplinify()is also chained the whole path becomes one absolute spline and this per-leg pass is skipped; when it isn’t, the builder appends this pass to do the per-leg conversion. It therefore leavesproducesat the default (SAME) so chaining with the terminalsplinify()is allowed.- name = 'to_absolute'¶