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 (
@@ -116,6 +120,26 @@ public Builder<C> withPoseFilter(Predicate<EstimatedRobotPose> isPoseValid) {
116120 return this ;
117121 }
118122
123+ /**
124+ * Set the Position Estimation Strategy used in multi-tag mode when only one tag can be seen.
125+ * Must NOT be {@link PhotonPoseEstimator.PoseStrategy#MULTI_TAG_PNP_ON_COPROCESSOR} or {@link
126+ * PhotonPoseEstimator.PoseStrategy#MULTI_TAG_PNP_ON_RIO}.
127+ *
128+ * <p>If this is not called, {@link PhotonPoseEstimator.PoseStrategy#LOWEST_AMBIGUITY} will be
129+ * used.
130+ *
131+ * @param strategy the strategy to set
132+ * @return Builder instance.
133+ * @since 2.1.0
134+ */
135+ public Builder <C > withMultiTagFallbackStrategy (PhotonPoseEstimator .PoseStrategy strategy ) {
136+ if (isMultiTagStrategy (strategy )) {
137+ throw new IllegalArgumentException ("Fallback strategy cannot be " + strategy );
138+ }
139+ fallbackStrategy = strategy ;
140+ return this ;
141+ }
142+
119143 /** Builds a configured MultiPhotonPoseEstimator. */
120144 public MultiPhotonPoseEstimator <C > build () {
121145 return new MultiPhotonPoseEstimator <>(this );
@@ -251,11 +275,13 @@ public void close() {
251275 /** Creates an instance using values from a {@code Builder}. */
252276 private MultiPhotonPoseEstimator (Builder <C > builder ) {
253277 poseEstimatorStrategy = builder .poseEstimatorStrategy ;
278+ fallbackStrategy = builder .fallbackStrategy ;
254279 isPoseValid = poseIsInField (builder .aprilTagFieldLayout ).and (builder .isPoseValid );
255280 cameraWrappers =
256281 builder .cameras .values ().stream ()
257282 .map (camera -> createCameraWrapper (builder , camera ))
258283 .collect (toCollection (ArrayList ::new ));
284+ needHeadingData = calculateNeedHeadingData ();
259285 }
260286
261287 /**
@@ -270,6 +296,7 @@ private static <C extends Camera> PhotonCameraWrapper<C> createCameraWrapper(
270296 PhotonPoseEstimator estimator =
271297 new PhotonPoseEstimator (
272298 builder .aprilTagFieldLayout , builder .poseEstimatorStrategy , camera .robotToCamera ());
299+ estimator .setMultiTagFallbackStrategy (builder .fallbackStrategy );
273300
274301 // Create NetworkTables publishers for 1) the position of the camera relative to the robot and
275302 // 2) the estimated position provided by the camera.
@@ -302,6 +329,7 @@ public void setPrimaryStrategy(PhotonPoseEstimator.PoseStrategy poseStrategy) {
302329 if (!poseStrategy .equals (poseEstimatorStrategy )) {
303330 cameraWrappers .forEach (wrapper -> wrapper .estimator .setPrimaryStrategy (poseStrategy ));
304331 poseEstimatorStrategy = poseStrategy ;
332+ needHeadingData = calculateNeedHeadingData ();
305333 }
306334 }
307335
@@ -311,10 +339,7 @@ public void setPrimaryStrategy(PhotonPoseEstimator.PoseStrategy poseStrategy) {
311339 * @return {@code true} if the pose strategy is documented to require addHeadingData().
312340 */
313341 public boolean poseStrategyRequiresHeadingData () {
314- return switch (poseEstimatorStrategy ) {
315- case PNP_DISTANCE_TRIG_SOLVE , CONSTRAINED_SOLVEPNP -> true ;
316- default -> false ;
317- };
342+ return needHeadingData ;
318343 }
319344
320345 /**
@@ -431,6 +456,29 @@ public void close() {
431456 cameraWrappers .clear ();
432457 }
433458
459+ private static boolean poseStrategyRequiresHeadingData (
460+ PhotonPoseEstimator .PoseStrategy strategy ) {
461+ return switch (strategy ) {
462+ case PNP_DISTANCE_TRIG_SOLVE , CONSTRAINED_SOLVEPNP -> true ;
463+ default -> false ;
464+ };
465+ }
466+
467+ private static boolean isMultiTagStrategy (PhotonPoseEstimator .PoseStrategy strategy ) {
468+ return switch (strategy ) {
469+ case MULTI_TAG_PNP_ON_COPROCESSOR , MULTI_TAG_PNP_ON_RIO -> true ;
470+ default -> false ;
471+ };
472+ }
473+
474+ private boolean calculateNeedHeadingData () {
475+ if (poseStrategyRequiresHeadingData (poseEstimatorStrategy )) {
476+ return true ;
477+ }
478+ return isMultiTagStrategy (poseEstimatorStrategy )
479+ && poseStrategyRequiresHeadingData (fallbackStrategy );
480+ }
481+
434482 /** Creates a predicate that determines if a pose is inside the field. */
435483 private static <C extends Camera > Predicate <EstimatedRobotPose > poseIsInField (
436484 AprilTagFieldLayout aprilTagFieldLayout ) {
0 commit comments