6767public class MultiPhotonPoseEstimator <C extends Camera > implements AutoCloseable {
6868 private final List <PhotonCameraWrapper <C >> cameraWrappers ;
6969 private final Predicate <EstimatedRobotPose > isPoseValid ;
70+ private final PhotonPoseEstimator .PoseStrategy fallbackStrategy ;
7071 private PhotonPoseEstimator .PoseStrategy poseEstimatorStrategy ;
72+ private boolean needHeadingData ;
7173
7274 /** A builder for {@code MultiPhotonPoseEstimator}. */
7375 public static final class Builder <C extends Camera > {
7476 private final Map <String , C > cameras = new HashMap <>();
7577 private final AprilTagFieldLayout aprilTagFieldLayout ;
7678 private final NetworkTableInstance ntInstance ;
7779 private final PhotonPoseEstimator .PoseStrategy poseEstimatorStrategy ;
80+ private PhotonPoseEstimator .PoseStrategy fallbackStrategy =
81+ PhotonPoseEstimator .PoseStrategy .LOWEST_AMBIGUITY ;
7882 private Predicate <EstimatedRobotPose > isPoseValid = pose -> true ;
7983
8084 Builder (
@@ -114,6 +118,24 @@ public Builder<C> withPoseFilter(Predicate<EstimatedRobotPose> isPoseValid) {
114118 return this ;
115119 }
116120
121+ /**
122+ * Set the Position Estimation Strategy used in multi-tag mode when only one tag can be seen.
123+ * Must NOT be {@link PhotonPoseEstimator.PoseStrategy#MULTI_TAG_PNP_ON_COPROCESSOR} or {@link
124+ * PhotonPoseEstimator.PoseStrategy#MULTI_TAG_PNP_ON_RIO}.
125+ *
126+ * <p>If this is not called, {@link PhotonPoseEstimator.PoseStrategy#LOWEST_AMBIGUITY} will be
127+ * used.
128+ *
129+ * @param strategy the strategy to set
130+ */
131+ public Builder <C > withMultiTagFallbackStrategy (PhotonPoseEstimator .PoseStrategy strategy ) {
132+ if (isMultiTagStrategy (strategy )) {
133+ throw new IllegalArgumentException ("Fallback strategy cannot be " + strategy );
134+ }
135+ fallbackStrategy = strategy ;
136+ return this ;
137+ }
138+
117139 /** Builds a configured MultiPhotonPoseEstimator. */
118140 public MultiPhotonPoseEstimator <C > build () {
119141 return new MultiPhotonPoseEstimator <>(this );
@@ -249,11 +271,13 @@ public void close() {
249271 /** Creates an instance using values from a {@code Builder}. */
250272 private MultiPhotonPoseEstimator (Builder <C > builder ) {
251273 poseEstimatorStrategy = builder .poseEstimatorStrategy ;
274+ fallbackStrategy = builder .fallbackStrategy ;
252275 isPoseValid = poseIsInField (builder .aprilTagFieldLayout ).and (builder .isPoseValid );
253276 cameraWrappers =
254277 builder .cameras .values ().stream ()
255278 .map (camera -> createCameraWrapper (builder , camera ))
256279 .collect (toCollection (ArrayList ::new ));
280+ needHeadingData = calculateNeedHeadingData ();
257281 }
258282
259283 /**
@@ -268,6 +292,7 @@ private static <C extends Camera> PhotonCameraWrapper<C> createCameraWrapper(
268292 PhotonPoseEstimator estimator =
269293 new PhotonPoseEstimator (
270294 builder .aprilTagFieldLayout , builder .poseEstimatorStrategy , camera .robotToCamera ());
295+ estimator .setMultiTagFallbackStrategy (builder .fallbackStrategy );
271296
272297 // Create NetworkTables publishers for 1) the position of the camera relative to the robot and
273298 // 2) the estimated position provided by the camera.
@@ -300,6 +325,7 @@ public void setPrimaryStrategy(PhotonPoseEstimator.PoseStrategy poseStrategy) {
300325 if (!poseStrategy .equals (poseEstimatorStrategy )) {
301326 cameraWrappers .forEach (wrapper -> wrapper .estimator .setPrimaryStrategy (poseStrategy ));
302327 poseEstimatorStrategy = poseStrategy ;
328+ needHeadingData = calculateNeedHeadingData ();
303329 }
304330 }
305331
@@ -309,10 +335,7 @@ public void setPrimaryStrategy(PhotonPoseEstimator.PoseStrategy poseStrategy) {
309335 * @return {@code true} if the pose strategy is documented to require addHeadingData().
310336 */
311337 public boolean poseStrategyRequiresHeadingData () {
312- return switch (poseEstimatorStrategy ) {
313- case PNP_DISTANCE_TRIG_SOLVE , CONSTRAINED_SOLVEPNP -> true ;
314- default -> false ;
315- };
338+ return needHeadingData ;
316339 }
317340
318341 /**
@@ -428,6 +451,29 @@ public void close() {
428451 cameraWrappers .clear ();
429452 }
430453
454+ private static boolean poseStrategyRequiresHeadingData (
455+ PhotonPoseEstimator .PoseStrategy strategy ) {
456+ return switch (strategy ) {
457+ case PNP_DISTANCE_TRIG_SOLVE , CONSTRAINED_SOLVEPNP -> true ;
458+ default -> false ;
459+ };
460+ }
461+
462+ private static boolean isMultiTagStrategy (PhotonPoseEstimator .PoseStrategy strategy ) {
463+ return switch (strategy ) {
464+ case MULTI_TAG_PNP_ON_COPROCESSOR , MULTI_TAG_PNP_ON_RIO -> true ;
465+ default -> false ;
466+ };
467+ }
468+
469+ private boolean calculateNeedHeadingData () {
470+ if (poseStrategyRequiresHeadingData (poseEstimatorStrategy )) {
471+ return true ;
472+ }
473+ return isMultiTagStrategy (poseEstimatorStrategy )
474+ && poseStrategyRequiresHeadingData (fallbackStrategy );
475+ }
476+
431477 /** Creates a predicate that determines if a pose is inside the field. */
432478 private static <C extends Camera > Predicate <EstimatedRobotPose > poseIsInField (
433479 AprilTagFieldLayout aprilTagFieldLayout ) {
0 commit comments