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
PathConstraintsappropriately 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.
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.
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.
.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.
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.
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().
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(): 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.
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)
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.
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.
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) // )
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 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.
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.
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.
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.
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 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.
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.
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
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 sameAutoBuilder.configure()setup from Lesson 7. If configure was not called, these methods throw a runtime exception. Verify by running a simplepathfindToPose()to a nearby pose in the shop before competition day. - preventFlipping is set correctly for runtime coordinate paths. For every
PathPlannerPathbuilt fromfromPathPoints()orwaypointsFromPoses(), verify whether the coordinates are blue-relative (let PathPlanner flip) or field-absolute (setpreventFlipping = 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?
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?
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?
Add Dynamic Path Generation to Your Autonomous
- Add a method
getAdaptiveScoreCommand(Pose2d scoringPose)toRobotContainerthat returnsAutoBuilder.pathfindToPose(scoringPose, navConstraints). Define three scoring poses as constants inAutoConstants: left, center, and right scoring positions. Bind each to a different controller button so you can test all three from the shop floor. - Add a second method
getPickupAndScoreCommand()that sequences:pathfindThenFollowPath("PickupPath", constraints)followed byIntakeUntilSensorCommandfollowed bypathfindThenFollowPath("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. - 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.
- Test the
preventFlippingbehavior: build a simplefromPathPoints()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 setpreventFlipping = trueand observe the difference. Document the observed behavior in code comments. - 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 apathfindToPose()to a game piece pickup location. Test by starting with and without a game piece loaded.