step.motion.path.passes.spline¶
Spline-conversion pass — translate a linear/turn sequence into a SplinePath.
Unlike merge / corner-cut (pure node-list transforms), this pass replaces the entire path with a single ABSOLUTE spline-driven step. It is therefore a “terminal” transform — typically the last entry in a pipeline that opts in to spline mode.
The pass densely samples the SAME centripetal Catmull-Rom curve used by the
relative SplinePath / C++ SplineMotion (see catmull_rom_spline.hpp)
and emits ONE GotoWaypoints side-action that
follows those dense samples CLOSED-LOOP on the localization particle filter —
so drift is corrected along the whole curve. The relative SplineMotion /
smooth_path(spline=True) path (which uses build_spline_step()
directly) is UNAFFECTED.
Classes¶
Terminal pass that collapses each motion RUN into one continuous spline. |
Functions¶
|
Densely sample a centripetal Catmull-Rom curve through |
|
Compute |
|
Build a SplinePath from a fully-splinifiable node list. |
Module Contents¶
- step.motion.path.passes.spline.sample_centripetal_catmull_rom(control_points_m: list[tuple[float, float]], spacing_m: float = _SAMPLE_SPACING_M) list[tuple[float, float]]¶
Densely sample a centripetal Catmull-Rom curve through
control_points_m.Matches
CatmullRomSplineincatmull_rom_spline.hppexactly:Centripetal parameterisation (alpha=0.5): knot spacing is the square root of the chord length between consecutive control points.
Virtual endpoints via REFLECTION (not duplication) so the curve passes through every control point:
P[-1] = 2*P[0] - P[1]andP[N] = 2*P[N-1] - P[N-2].Barry-Goldman segment evaluation.
Returns a flat list of
(x_m, y_m)points, including the first and last control points, with consecutive points roughlyspacing_mapart so the full path from the first to the last control point is covered.- Parameters:
control_points_m – At least 2 control points
(x_m, y_m).spacing_m – Target arc-length spacing between samples (metres).
- Returns:
Dense
[(x_m, y_m), ...]samples along the curve.
- step.motion.path.passes.spline.segments_to_spline_waypoints(segments: list[step.motion.path.ir.Segment]) list[tuple[float, float]]¶
Compute
(forward_cm, left_cm)control waypoints from a segment sequence.Walks the SAME body-frame pose integration as
to_absolute(+forward is the robot’s initial heading, +left is 90° CCW, heading CCW-positive):linearadvances along its axis and emits the endpoint.diagonalholds heading and advances by its known body-frame displacement(forward_m, left_m)rotated into world, emitting the endpoint.arcis SAMPLED into several intermediate control points (≈ one every 18° of sweep, min 2) so the Catmull-Rom traces the arc’s curvature; the running pose advances to the arc’s exact endpoint/heading.turnonly updates the running heading (no waypoint) — the corner is rounded by the Catmull-Rom through the neighbouring waypoints.
The robot’s start position
(0, 0)is not included — it is the implicit origin forspline().
- step.motion.path.passes.spline.build_spline_step(nodes: list[step.motion.path.ir.PathNode | None]) step.motion.spline_path.SplinePath¶
Build a SplinePath from a fully-splinifiable node list.
Strict, single-spline builder used by
smooth_path(spline=True): raisesValueErrorfor any node that cannot be represented as a waypoint — deferred placeholders, side actions, condition-based segments,follow_line/splinesegments, or segments with unknown endpoints. Arcs and diagonals ARE accepted — they are integrated into control waypoints the Catmull-Rom traces (arcs are sampled along their curvature). A minimum of 2 control waypoints (after sampling) is required so the spline has at least 2 explicit control points.The
optimize().splinify()pass does NOT use this — it splits at barriers instead (seeSplinifyPass).
- class step.motion.path.passes.spline.SplinifyPass(absolute: bool = False)¶
Terminal pass that collapses each motion RUN into one continuous spline.
Rather than demanding the whole path be a single uninterrupted curve, the pass SPLITS at barriers — exactly like
merge/cut_corners/to_absolutealready do — so a path that mixes motion with side effects still splinifies cleanly instead of raising:A blocking (inline) side action, a deferred (
None) placeholder, a condition-based / unknown-endpoint segment, afollow_line/splinesegment, or aturn_to_headingBREAKS the run: it is passed through unchanged and the motion on either side becomes a separate spline. (A blocking side action must stop the robot anyway, so the velocity break at that one point is unavoidable — the step still fires at the same place.)A background side action does NOT break the run: the spline flows across it (velocity stays continuous) and the step is lifted to the run’s start, launched fire-and-forget so it overlaps the motion.
Each maximal run of splinifiable segments (
linear/diagonal/arc/ plainturn) with ≥ 2 control waypoints becomes one spline; a run too short to splinify (a lone linear, or turns only) is kept as its raw segments.Two render modes, chosen by the builder from whether
to_absolute()is on:relative (default) →
Segment(kind="spline")per run: the continuous, odometry-driven C++SplineMotionfollows the centripetal Catmull-Rom.absolute →
SideAction(SplineFollow)per run: a continuous pure-pursuit follower rides the SAME curve closed-loop on the localization particle filter, correcting drift along the whole curve. Holonomic — heading held, independent of the path tangent.
Terminal: it replaces every motion run wholesale, so nothing may run after it.
- name = 'splinify'¶
- requires¶
- terminal = True¶
- absolute = False¶