step.motion.smooth_path

Smooth multi-segment motion path with zero-stop transitions.

Supports bare motion steps, nested seq(), parallel() with a motion spine, background() steps, and Run actions. Composite steps are flattened into a linear path of motion segments and side actions at construction time, with deferred steps resolved at runtime.

Classes

SmoothPath

Execute a sequence of motion segments with smooth velocity transitions.

Functions

smooth_path(→ SmoothPath)

Execute motion steps with smooth velocity transitions between them.

Module Contents

class step.motion.smooth_path.SmoothPath(steps: list, correct: bool = True, optimize: bool = False, corner_cut_m: float = 0.0, spline: bool = False)

Bases: step.Step

Execute a sequence of motion segments with smooth velocity transitions.

Instead of decelerating to zero between each step, carries velocity across segment boundaries using warm-started motion controllers.

Supports composite steps: nested seq() is flattened, parallel() with a motion spine runs side-effect branches concurrently, background() steps are launched without blocking, and non-drive steps (servos, Run actions, etc.) execute at transition points.

hz: int = 100
required_resources() frozenset[str]

Return the hardware resources this step requires exclusive access to.

For leaf steps (drive, motor, servo), return the resources this step directly uses. Composite steps override collected_resources instead to include children — required_resources stays empty for composites because they don’t touch hardware themselves.

collected_resources() frozenset[str]

Return all resources this step and its children require.

Used by validate_no_overlap for static conflict detection at construction time. Leaf steps don’t need to override this — the default delegates to required_resources. Composite steps override to union their children’s collected resources.

step.motion.smooth_path.smooth_path(*steps, correct: bool = True, optimize: bool = False, corner_cut_cm: float | None = None, spline: bool = False) SmoothPath

Execute motion steps with smooth velocity transitions between them.

Eliminates the velocity drops that occur when steps are chained via seq(). Instead of decelerating to zero and hard-stopping between each step, smooth_path carries velocity across segment boundaries, producing continuous fluid motion.

For same-type transitions (e.g., drive to drive), the velocity is carried seamlessly via warm-started motion controllers with zero discontinuity. For cross-type transitions (e.g., drive to turn), a soft stop is used which smoothly decelerates without resetting PID state.

When correct is enabled (default), the path tracks the robot’s world-frame pose and corrects accumulated position and heading errors at each segment transition. Along-track errors adjust the next linear segment’s distance, cross-track errors bias the heading hold, and heading errors adjust turn/arc angles. All corrections are clamped to safe limits (3 cm distance, 5 deg heading).

Supports composite steps:

  • Nested seq(): Automatically flattened into the motion spine.

  • parallel(): The branch containing drive steps becomes the motion spine; other branches run concurrently as side effects.

  • background(): Launched without blocking the motion flow.

  • Non-drive steps (servos, Run actions, etc.): Execute at transition points between motion segments.

Steps with .until() conditions are fully supported. When a condition fires, the current velocity is carried into the next segment immediately.

Supports Defer steps (e.g., turn_to_heading_right), which are resolved lazily at the actual transition point so that heading-dependent steps see the robot’s current heading, not the heading at the start of the path.

Optimization (all opt-in):

optimize=True applies algebraic simplification passes at construction time, before execution:

  • Merge: adjacent same-type, same-direction, same-axis segments with no conditions are collapsed into one. drive(30) + drive(20) becomes drive(50); turn(30°) + turn(20°) becomes turn(50°). Only same-direction merges are performed (drive(30) + drive(-20) is not merged — intent may differ from net displacement). _SideAction and deferred nodes act as merge barriers.

corner_cut_cm=N (implies algebraic merge as well) replaces linear + turn + linear triples with linear + arc + linear by cutting N cm from each straight leg and inserting a circular arc of radius R = N / tan(|θ| / 2) at the corner. The robot never slows for the turn — it follows the arc at cruise speed. Only segments with known endpoints and no conditions are eligible.

Note on _SideAction barriers: a background() or non-drive step between two segments pins that step to the transition point. Optimization never reorders across side actions, preserving the execution contract.

Prerequisites:

calibrate_distance() if any segment uses distance-based mode. mark_heading_reference() if any segment uses heading hold.

Parameters:
  • *steps – Motion steps to execute smoothly. Accepts drive_forward, drive_backward, strafe_left, strafe_right, turn_left, turn_right, turn_to_heading_left, turn_to_heading_right, drive_arc_left, drive_arc_right, follow_line, follow_line_single, spline, nested seq(), parallel() with a motion spine, background(), and their builder variants with .until() and .speed(). follow_line / follow_line_single steps warm-start from a preceding forward drive segment; the subsequent segment cold-starts from the line-follow exit velocity. spline steps always cold-start and exit at zero velocity.

  • correct – Enable world-frame error correction across segment transitions. Default True. Set to False for the legacy uncorrected behavior.

  • optimize – Merge adjacent same-type segments algebraically. Default False (opt-in).

  • corner_cut_cm – If set, replace right-angle drive+turn+drive corners with arcs of radius corner_cut_cm / tan(|θ|/2), cutting corner_cut_cm cm from each straight leg. Also enables the merge pass. Mutually exclusive with spline. Default None (disabled).

  • spline – If True, convert the drive/turn sequence into waypoints and follow them with a Catmull-Rom spline (SplinePath). The entire path executes as a single continuous curve — no segment transitions, no velocity drops. Requires all segments to have known endpoints and no conditions or side actions. Mutually exclusive with corner_cut_cm. Default False (disabled).

Returns:

A SmoothPath step that executes the segments as one fluid motion.

Example:

from raccoon.step.motion import smooth_path, drive_forward, turn_to_heading_right
from raccoon.step.condition import on_black

# Three drives with no stops between them
smooth_path(
    drive_forward(50),
    drive_forward(30),
    drive_forward(20),
)

# Drive, turn, drive — soft transition at type boundaries
smooth_path(
    drive_forward(50),
    turn_to_heading_right(90),
    drive_forward(30),
)

# Merge adjacent drives and cut corners with 5 cm arcs
smooth_path(
    drive_forward(30),
    drive_forward(20),
    turn_left(90),
    drive_forward(40),
    optimize=True,
    corner_cut_cm=5.0,
)
# → drive(50) + arc(R=5cm, 90°) + drive(35)

# Condition-based segment carries velocity into the next
smooth_path(
    drive_forward(speed=0.8).until(on_black(sensor)),
    drive_forward(20),
)

# Parallel: servo moves while driving continuously
smooth_path(
    drive_forward(30),
    parallel(
        [drive_forward(20), drive_forward(10)],
        [servo_move(0, 1500)],
    ),
)

# Background: non-blocking step alongside motion
smooth_path(
    drive_forward(30),
    background(servo_move(0, 1500)),
    drive_forward(20),
)

# Disable correction for legacy behavior
smooth_path(
    drive_forward(50),
    turn_to_heading_right(90),
    drive_forward(30),
    correct=False,
)

# Spline mode: drive+turn+drive → smooth curved path via Catmull-Rom.
# The robot follows the spline tangent; the drive and turn steps provide
# the waypoints and heading changes.  Requires at least 2 linear
# segments; raises ValueError for condition-based steps, side actions,
# arc segments, or fewer than 2 waypoints.
smooth_path(
    drive_forward(50),
    turn_right(90),
    drive_forward(30),
    spline=True,
)