Unit 9 · Lesson 9

PathPlanner: On-the-Fly Path Generation

Pre-planned paths assume the robot is exactly where you expect it to be. On-the-fly path generation starts from wherever the robot actually is — no matter how it got there — and computes the path to the target at runtime. This is how autonomous routines recover from defense, missed game pieces, and vision-corrected positioning.

By the end of this lesson, you will:

  • Explain the difference between pre-planned path following and on-the-fly path generation
  • Use AutoBuilder.pathfindToPose() to drive to an arbitrary field position from any starting pose
  • Use AutoBuilder.pathfindThenFollowPath() to transition smoothly from pathfinding into a pre-drawn path
  • Build a path at runtime from code-specified waypoints using PathPlannerPath.fromPathPoints()
  • Configure PathConstraints appropriately for dynamic paths that must compute quickly
  • Identify the three situations where on-the-fly generation is the right approach and two where it isn't

Pre-Planned vs. On-the-Fly: The Core Distinction

Everything you built in Lessons 7 and 8 involves pre-planned paths: you drew the path in the GUI, saved it to a .path file, and the robot followed it from a known starting pose. The path geometry and constraints are fixed before the match begins. The robot must start where the path assumes it starts — otherwise the controller corrects for a phantom pose error throughout the whole path.

On-the-fly path generation flips this. Instead of "the robot follows this predetermined path," it becomes "compute the best path from wherever the robot currently is to this target pose, right now." The path is generated at runtime using the robot's live odometry as the starting point. The result is a command that navigates from any starting condition to any target — even if the robot was bumped, took an alternate scoring route, or is responding to a vision update that revised the target position.

PathPlanner's on-the-fly API uses the same holonomic controller and constraint system from pre-planned paths. The difference is entirely in how the trajectory is constructed: from a file on disk vs. computed on the roboRIO during the match.

💡 On-the-fly generation has a computational cost

Generating a new path on the roboRIO takes 1–5 ms depending on path complexity and constraints. This is negligible for a single generation event, but it's not zero. Don't call pathfindToPose() inside periodic() on every loop — call it once to create a command and schedule that command. The generation happens when the command is created, not when it runs. If you need to re-target mid-path (e.g., because a new vision measurement arrived), schedule a new pathfindToPose() command which will preempt the previous one via requirement interruption.

The Three On-the-Fly APIs

PathPlanner provides three distinct approaches for runtime path generation, each suited to a different use case.

AutoBuilder.pathfindToPose()
Drive to any pose from current position Generates a complete path from the robot's current odometry pose to a specified target Pose2d. No pre-drawn path file needed — PathPlanner computes the trajectory at call time. Ends when the robot arrives at the target pose within tolerance.
When to use: autonomous corrections after defense contact; vision-updated scoring positions; teleop driver assist "drive to pickup location."
AutoBuilder.pathfindThenFollowPath()
Navigate to a path's start, then follow it Generates an approach path from the robot's current pose to the starting waypoint of a named .path file, then transitions seamlessly into following that file's trajectory. Combines the flexibility of on-the-fly with the precision of a pre-tuned path.
When to use: when the robot might not start exactly at the path's first waypoint; graceful recovery from early autonomous errors; multi-route routines where different starts lead to the same pre-planned scoring path.
PathPlannerPath.fromPathPoints()
Build a path from code-specified waypoints Creates a PathPlannerPath object entirely in Java from a list of waypoints, headings, and constraints — no GUI file involved. The path can then be followed with AutoBuilder.followPath(). Most useful for programmatically generated routes where target positions come from vision or game state.
When to use: when target positions are computed at runtime (from vision measurements, game piece detection, or alliance-provided coordinates).
AutoBuilder.followPath()
Follow any PathPlannerPath object Follows a PathPlannerPath object — whether loaded from a file or built at runtime. Used in combination with fromPathPoints() to execute a programmatically generated path, or to follow a loaded path without buildAuto().
When to use: as the execution step after fromPathPoints(); when you need direct control over path construction parameters that the GUI doesn't expose.

Pathfinding Demo: Dynamic Route to Target

The demo below shows pathfindToPose() in action. The robot starts at the left side of the field. Drag the target marker to any position, add obstacles, and watch PathPlanner compute a path around them. Change the robot's starting position and regenerate — the path always begins from the robot's current pose, regardless of where that is.

pathfindToPose() — dynamic path generation click Generate to plan a path
Robot start position
1.0
4.0
Target & constraints
12.0
5.5
3.5

pathfindToPose(): Drive to Any Target

AutoBuilder.pathfindToPose() is the simplest and most powerful on-the-fly API. You give it a target pose and constraints; it returns a command that drives the robot there from wherever it currently is. The robot's live odometry pose at command start time becomes the path's starting waypoint.

AutoBuilder.pathfindToPose() — basic usage
import com.pathplanner.lib.auto.AutoBuilder;
import com.pathplanner.lib.path.PathConstraints;

// Target pose: a specific scoring position, computed from game state or vision
Pose2d targetPose = new Pose2d(
    14.0,  // x: meters from alliance wall
    5.5,   // y: meters from field wall
    Rotation2d.fromDegrees(180));  // heading: face the target

// Constraints for the dynamically generated path
// Use conservative values — the path is generated once and not tuned
PathConstraints constraints = new PathConstraints(
    3.0,    // maxVelocity m/s — slightly lower than tuned path speed
    3.0,    // maxAcceleration m/s²
    2 * Math.PI,  // maxAngularVelocity rad/s
    4 * Math.PI   // maxAngularAcceleration rad/s²
);

// Create the command — path is generated at this point, not when the command runs
Command driveToTarget = AutoBuilder.pathfindToPose(targetPose, constraints);

// In a Command-Based context, schedule it:
// driveToTarget.schedule();
// Or use it in a command composition:
// Commands.sequence(driveToTarget, scoreCommand)
💡 Call pathfindToPose() once, then schedule the returned Command

The common mistake is to create a new pathfindToPose() call inside execute() every loop. This regenerates the path 50 times per second, which is extremely expensive and causes the robot to constantly restart the path from its updated position — effectively never completing it. Create the command once (when the drive target is decided), schedule it, and let it run to completion. If the target changes mid-path, cancel the current command and create+schedule a new one with the updated target pose.

pathfindThenFollowPath(): Bridge to Precision

On-the-fly paths are flexible but not as precisely tuned as paths you've drawn and tested in the GUI. For scoring actions that require the robot to arrive at an exact position with a calibrated approach angle, pathfindThenFollowPath() gives you the best of both: dynamic navigation to the vicinity of a pre-drawn path, then precision execution of that tuned path once the robot is close enough.

AutoBuilder.pathfindThenFollowPath() — dynamic approach + precision finish
import com.pathplanner.lib.path.PathPlannerPath;

// Load the pre-drawn precision scoring path from the deploy directory
// This path was drawn in the GUI and carefully tuned (Lesson 7-8)
PathPlannerPath scoringPath = PathPlannerPath.fromPathFile("SpeakerApproach");

PathConstraints approachConstraints = new PathConstraints(
    3.0, 3.0,
    2 * Math.PI, 4 * Math.PI);

// Phase 1: Dynamic path from current robot pose to SpeakerApproach's start waypoint
// Phase 2: Follow SpeakerApproach exactly as drawn in the GUI
// The transition between phases is seamless — velocity continuity is maintained
Command navToScore = AutoBuilder.pathfindThenFollowPath(
    scoringPath,          // the path to arrive at and then follow
    approachConstraints   // constraints for the dynamic approach segment
);

// Usage in a command group:
// Commands.sequence(
//     intakeGamePiece,          // sensor-based intake (Lesson 3)
//     navToScore,               // dynamic approach + precision finish
//     shootCommand              // fire at precisely positioned robot
// )

This pattern appears throughout Team 2910's code: pre-drawn paths for scoring approaches where millimeters matter, and dynamic pathfinding for everything that gets the robot to the vicinity of those approaches. The dynamic segment doesn't need to be perfect — it just needs to get the robot close enough that the pre-drawn path's starting conditions are reasonable.

PathPlannerPath.fromPathPoints(): Pure Code Paths

Sometimes the target position isn't known until the match is running. Vision tells you a game piece is at coordinates (8.3, 2.1). The field position of a defended zone changes. An alliance partner's auto was aborted and you need a different path. In these cases, there's no GUI path to load — the path must be built entirely in code from runtime data.

PathPlannerPath.fromPathPoints() builds a PathPlannerPath from a list of PathPoint objects, each specifying position, heading, and holonomic rotation. Combined with AutoBuilder.followPath(), this gives you a fully code-generated path that the same holonomic controller follows.

PathPlannerPath.fromPathPoints() — building a path from runtime data
import com.pathplanner.lib.path.*;

// Example: navigate to a vision-detected game piece at a runtime position
// gamePiecePosition would come from PhotonVision or similar (Unit 10)
Translation2d gamePiecePosition = visionSubsystem.getGamePiecePose();

// Build waypoints: current pose → intermediate point → game piece location
List<Waypoint> waypoints = PathPlannerPath.waypointsFromPoses(
    m_drive.getPose(),   // start: current odometry pose
    new Pose2d(gamePiecePosition, Rotation2d.fromDegrees(0))  // end: face piece
);

PathConstraints constraints = new PathConstraints(
    2.5, 2.5,
    2 * Math.PI, 4 * Math.PI
);

// Path configuration: rotation targets, end conditions, event markers
GoalEndState goalEndState = new GoalEndState(
    0.0,   // end velocity: stop at the game piece
    Rotation2d.fromDegrees(0));  // end rotation: intake faces forward

PathPlannerPath path = new PathPlannerPath(
    waypoints, constraints, null, goalEndState);

// Prevent the path from being flipped (we already have field-relative coords)
path.preventFlipping = true;

// Follow the path with the standard holonomic controller
Command pickupCommand = AutoBuilder.followPath(path);

// Compose with intake and return:
// Commands.sequence(
//     pickupCommand,
//     new IntakeUntilSensorCommand(m_intake),
//     AutoBuilder.pathfindThenFollowPath(scoringApproach, constraints)
// )
⚠️ Set preventFlipping = true when using runtime coordinates

PathPlanner's alliance flip system (from Lesson 7's lambda) mirrors paths from blue to red. If you build a path from field-relative coordinates that already account for the robot's alliance, you don't want PathPlanner to mirror them again. Call path.preventFlipping = true on any path built from runtime data that already uses field-absolute coordinates. If you forget this, red alliance robots will have their dynamically generated paths mirrored — driving to the wrong side of the field.

When to Use On-the-Fly Generation

🎯 Vision-corrected scoring pathfindThenFollowPath()

Vision measurement updates the target pose mid-auto. Cancel the current path, call pathfindThenFollowPath() with the refined pose. The robot recalculates from its current position rather than restarting the pre-drawn path from scratch.

🔄 Recovery from defense pathfindToPose()

Another robot pushed yours off the pre-planned trajectory. The next step of the auto needs the robot at a specific position. pathfindToPose() navigates there regardless of where the defense pushed you, then the auto continues.

🎮 Teleop driver assist pathfindToPose()

Bind a controller button to a "drive to amp" or "drive to speaker" command that navigates to a fixed scoring zone. The driver aims, presses the button, and the robot handles pathfinding while they focus on game piece placement. Cancel on button release.

📍 Runtime game piece pickup fromPathPoints()

Vision detects a game piece that isn't in a predetermined location. Build a path to the detected position at runtime. The path ends with the intake facing the piece at zero velocity — ready for acquisition.

🗺️ Multi-strategy auto branching pathfindThenFollowPath()

Autonomous detects (via sensor or game state) that a game piece isn't where expected. Branch to a different scoring path using pathfindThenFollowPath() from current position — the robot adapts without restarting from the beginning.

🚫 When NOT to use it use pre-planned .path files instead

When the path starts from a known position and doesn't need to adapt. When the target pose is fixed and you've tuned a path to it. When precision at the endpoint is critical — pre-planned paths with tuned gains outperform dynamically generated ones at the scoring position itself.

🔍 How championship teams use dynamic paths

Teams like 2910 and 6328 use on-the-fly generation selectively, not as a replacement for pre-planned paths. The typical pattern: all scoring paths are pre-drawn and tuned for exact positioning at the endpoint. Dynamic pathfinding handles recovery — if the robot is somewhere it shouldn't be at the start of a scoring cycle, pathfindThenFollowPath() navigates to the pre-drawn path's entry point. The scoring itself always uses the pre-tuned path. This hybrid keeps the benefits of both: adaptability for navigation, precision for scoring.

Integrating Dynamic Paths Into Command Groups

On-the-fly path commands are regular Command objects. They work identically to pre-planned path commands inside command groups — they can be sequenced, run in parallel with mechanism actions, and given timeouts.

RobotContainer.java — dynamic path in a complete autonomous sequence
public Command getAdaptiveAuto() {
    PathConstraints navConstraints = new PathConstraints(
        3.0, 3.0, 2*Math.PI, 4*Math.PI);

    return Commands.sequence(

        // Phase 1: Follow pre-planned scoring path (precision required)
        AutoBuilder.buildAuto("ScorePreload"),

        // Phase 2: Dynamic approach to nearest game piece + intake
        // pathfindThenFollowPath navigates to PickupLeft.path's entry,
        // then follows the precision pickup path.
        // The dynamic approach adapts if we're not exactly at the expected pose.
        Commands.deadline(
            AutoBuilder.pathfindThenFollowPath(
                PathPlannerPath.fromPathFile("PickupLeft"),
                navConstraints),
            new IntakeUntilSensorCommand(m_intake).withTimeout(4.0)
        ),

        // Phase 3: Navigate back to speaker — dynamic, because game piece pickup
        // position may have varied, so robot may not be at a predicted pose.
        Commands.parallel(
            AutoBuilder.pathfindThenFollowPath(
                PathPlannerPath.fromPathFile("SpeakerApproach"),
                navConstraints),
            new SpinUpShooterCommand(m_shooter, 4000).withTimeout(3.0)
        ),

        // Phase 4: Score second piece
        Commands.parallelRace(
            new EjectCommand(m_shooter),
            Commands.waitSeconds(0.6))
    );
}

🔌 System Check

⚙️ Before Relying on On-the-Fly Paths in Competition

Dynamic path generation is powerful but adds complexity. Verify each of these before a match:

  • AutoBuilder is configured before any on-the-fly calls. pathfindToPose() and related methods use the same AutoBuilder.configure() setup from Lesson 7. If configure was not called, these methods throw a runtime exception. Verify by running a simple pathfindToPose() to a nearby pose in the shop before competition day.
  • preventFlipping is set correctly for runtime coordinate paths. For every PathPlannerPath built from fromPathPoints() or waypointsFromPoses(), verify whether the coordinates are blue-relative (let PathPlanner flip) or field-absolute (set preventFlipping = true). Getting this wrong sends the robot to the wrong side of the field on red alliance.
  • Path generation is called once, not in a loop. Profile the roboRIO loop time before and after adding on-the-fly generation. If loop time increases significantly, path generation may be called more often than expected. Use SmartDashboard to log a counter that increments each time a new path is generated — it should be 1 per auto phase, not 50.
  • Dynamic paths have .withTimeout() safety nets. On-the-fly paths don't have the exact endpoint guarantee of tuned pre-planned paths. Add .withTimeout() to every dynamic path command so the sequence can proceed even if the path doesn't complete within expected time.
  • Test the full sequence from multiple starting positions. The whole point of dynamic paths is to handle unexpected starting conditions. Test your autonomous from at least three different starting poses — the planned one, and two positions offset by 20–30 cm in different directions. Confirm the robot navigates to the correct target from all three.

Knowledge Check

1. A team wants to score a game piece as accurately as possible at a speaker opening that requires the robot to approach from a precise angle. They currently use pathfindToPose() directly to the scoring position, but the endpoint accuracy is lower than their tuned pre-drawn paths. What is the correct architectural improvement?

  • A Increase maxVelocity in the dynamic path constraints to arrive faster
  • B Tune the translation kP for the dynamic path separately from the pre-planned path gains
  • C Switch to pathfindThenFollowPath() using a pre-drawn, tuned approach path — dynamic navigation handles getting near the target, then the precision path takes over for the final approach
  • D Use fromPathPoints() to build the scoring path at runtime with more waypoints for better accuracy

2. A programmer calls AutoBuilder.pathfindToPose(visionTarget, constraints) inside periodic() every loop, where visionTarget updates from a PhotonVision camera. The robot jerks and never settles on a path. What is the cause and fix?

  • A PhotonVision targets are not precise enough to use with PathPlanner — use a different sensor
  • B A new path is generated and a new command is scheduled every 20 ms — the robot constantly restarts the path from a new starting pose before making progress. Fix: generate the command once when the target is confirmed, schedule it, and let it complete; use a debounce on the vision target before triggering path generation
  • C The constraints need to be increased so the path completes faster than the 20 ms loop
  • D pathfindToPose() cannot be called with a Pose2d computed from vision — it requires a hardcoded constant pose

3. A team builds a dynamic path using PathPlannerPath.fromPathPoints() with field-absolute coordinates. During a red alliance match, the robot drives to the incorrect position — mirroring across the field center. What was likely missed?

  • A The path constraints need to specify red alliance mode
  • B path.preventFlipping = true was not set — PathPlanner's alliance flip lambda mirrors the path for red alliance, but since the coordinates were already field-absolute, this produces a double-mirroring that sends the robot to the wrong side
  • C The waypointsFromPoses() method doesn't support red alliance paths
  • D The alliance flip lambda in AutoBuilder.configure() is not called for dynamically generated paths
💪 Practice Prompt

Add Dynamic Path Generation to Your Autonomous

  1. Add a method getAdaptiveScoreCommand(Pose2d scoringPose) to RobotContainer that returns AutoBuilder.pathfindToPose(scoringPose, navConstraints). Define three scoring poses as constants in AutoConstants: left, center, and right scoring positions. Bind each to a different controller button so you can test all three from the shop floor.
  2. Add a second method getPickupAndScoreCommand() that sequences: pathfindThenFollowPath("PickupPath", constraints) followed by IntakeUntilSensorCommand followed by pathfindThenFollowPath("ScoringApproach", constraints). Wrap each dynamic path in a .withTimeout() appropriate for the path length. Test from two different starting positions and confirm the robot arrives at the scoring position both times.
  3. Add a SmartDashboard counter that logs how many times a new path is generated during a single autonomous run. Deploy and run your adaptive auto. Confirm the counter increments once per phase, not continuously. If you see unexpected increments, identify which code path is triggering extra generation.
  4. Test the preventFlipping behavior: build a simple fromPathPoints() path with explicit field coordinates and run it on both blue and red alliance (change the DS setting). Observe where the robot drives in each case. Then set preventFlipping = true and observe the difference. Document the observed behavior in code comments.
  5. Bonus: Implement a simple auto branching pattern: use Commands.either() to choose between two paths based on whether a game piece sensor is tripped at the start of autonomous. If the piece is detected, run the full scoring path. If not, run a pathfindToPose() to a game piece pickup location. Test by starting with and without a game piece loaded.