step.motion.smooth_path ======================= .. py:module:: step.motion.smooth_path .. autoapi-nested-parse:: 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 ------- .. autoapisummary:: step.motion.smooth_path.SmoothPath Functions --------- .. autoapisummary:: step.motion.smooth_path.smooth_path Module Contents --------------- .. py:class:: SmoothPath(steps: list, correct: bool = True, optimize: bool = False, corner_cut_m: float = 0.0, spline: bool = False) Bases: :py:obj:`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. .. py:attribute:: hz :type: int :value: 100 .. py:method:: 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. .. py:method:: 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. .. py:function:: 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. :param \*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. :param correct: Enable world-frame error correction across segment transitions. Default ``True``. Set to ``False`` for the legacy uncorrected behavior. :param optimize: Merge adjacent same-type segments algebraically. Default ``False`` (opt-in). :param 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). :param 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, )