Repository: acts-project/acts
Pull request #4105: refactor: Migrate Material node to overload based API
Author: login: paulgessinger
Labels: name: Component - Core
description: Affects the Core module
name: Component - Examples
description: Affects the Examples module
Created: 2025-02-20T15:44:53Z | Merged: 2025-03-06T06:45:33Z

This restructures the material designator blueprint node and removes the external access to the internal variant. There are now multiple method overloads based on the face enum type of the corresponding volume, and that will identify the volume mode to configure. This allows throwing an error earlier than before, where this would only become clear during the actual building process.

Blocked by:

- https://github.com/acts-project/acts/pull/4047

--- END COMMIT MESSAGE ---

Any further description goes here, @-mentions are ok here!

- Use a *conventional commits* prefix: [quick summary](https://www.conventionalcommits.org/en/v1.0.0/#summary)
  - We mostly use `feat`, `fix`, `refactor`, `docs`, `chore` and `build` types.
- A milestone will be assigned by one of the maintainers


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Now supports cuboid volume configurations in addition to cylinders.
  - The user-facing API has been enhanced with explicit methods for setting face configurations for different volume shapes.
  - Improved logging and error handling for both cylinder and cuboid volume bounds.

- **Refactor**
  - Replaced the previous property-based approach with a refined, method-based configuration system.
  - Enhanced validation and error handling ensure consistent and reliable material setups.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/Core/include/Acts/Geometry/CuboidPortalShell.hpp b/Core/include/Acts/Geometry/CuboidPortalShell.hpp
index db97293e41d..cbf14e27c17 100644
--- a/Core/include/Acts/Geometry/CuboidPortalShell.hpp
+++ b/Core/include/Acts/Geometry/CuboidPortalShell.hpp
@@ -28,8 +28,6 @@ class CuboidPortalShell : public PortalShellBase {
  public:
   using Face = CuboidVolumeBounds::Face;
 
-  using enum CuboidVolumeBounds::Face;
-
   /// Retrieve the portal associated to the given face. Can be nullptr if unset.
   /// @param face The face to retrieve the portal for
   /// @return The portal associated to the face
@@ -142,12 +140,13 @@ class CuboidStackPortalShell final : public CuboidPortalShell {
   AxisDirection m_direction;
 
   /// The cuboid face positioned first along the stacking direction
-  CuboidVolumeBounds::Face m_frontFace = NegativeXFace;
+  CuboidVolumeBounds::Face m_frontFace = Face::NegativeXFace;
   /// The cuboid face positioned last along the stacking direction
-  CuboidVolumeBounds::Face m_backFace = PositiveXFace;
+  CuboidVolumeBounds::Face m_backFace = Face::PositiveXFace;
   /// The cuboid faces parallel to the stacking direction
   std::array<CuboidVolumeBounds::Face, 4> m_sideFaces{
-      NegativeZFace, PositiveZFace, NegativeYFace, PositiveYFace};
+      Face::NegativeZFace, Face::PositiveZFace, Face::NegativeYFace,
+      Face::PositiveYFace};
 
   std::vector<CuboidPortalShell*> m_shells;
 };
diff --git a/Core/include/Acts/Geometry/CuboidVolumeBounds.hpp b/Core/include/Acts/Geometry/CuboidVolumeBounds.hpp
index 04bfbc02de4..b31f398f12a 100644
--- a/Core/include/Acts/Geometry/CuboidVolumeBounds.hpp
+++ b/Core/include/Acts/Geometry/CuboidVolumeBounds.hpp
@@ -201,4 +201,7 @@ class CuboidVolumeBounds : public VolumeBounds {
   /// will throw a logic_exception if consistency is not given
   void checkConsistency() noexcept(false);
 };
+
+std::ostream& operator<<(std::ostream& os, CuboidVolumeBounds::Face face);
+
 }  // namespace Acts
diff --git a/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp b/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp
index fb55bff9948..1bef13eee06 100644
--- a/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp
+++ b/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp
@@ -9,6 +9,8 @@
 #pragma once
 
 #include "Acts/Geometry/BlueprintNode.hpp"
+#include "Acts/Geometry/CuboidPortalShell.hpp"
+#include "Acts/Geometry/CuboidVolumeBounds.hpp"
 #include "Acts/Geometry/CylinderPortalShell.hpp"
 #include "Acts/Utilities/ProtoAxis.hpp"
 
@@ -21,13 +23,11 @@ namespace Acts::Experimental {
 /// mark up, and how do to so.
 /// @note This node can only have a single child. This is not an error during
 ///       tree building, but during geometry construction.
-/// @note This currently only supports a cylinder volume child
 class MaterialDesignatorBlueprintNode final : public BlueprintNode {
  public:
-  // @TODO: This needs cuboid volume storage as well
-  // @TODO: I don't love the type
-  using BinningConfig = std::variant<std::vector<
-      std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>>;
+  using BinningConfig = std::variant<
+      std::vector<std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>,
+      std::vector<std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>;
 
   /// Main constructor for the material designator node.
   /// @param name The name of the node (for debug only)
@@ -73,13 +73,31 @@ class MaterialDesignatorBlueprintNode final : public BlueprintNode {
   void finalize(const BlueprintOptions& options, const GeometryContext& gctx,
                 TrackingVolume& parent, const Logger& logger) override;
 
-  /// Retrieve the binning configuration
-  /// @return The binning configuration
-  const std::optional<BinningConfig>& binning() const;
-
-  /// Set the binning configuration
-  /// @param binning The binning configuration
-  MaterialDesignatorBlueprintNode& setBinning(BinningConfig binning);
+  /// Configure the designator with a cylinder face and corresponding binning
+  /// information.
+  /// @note This method can be called multiple times to configure different faces.
+  /// @param face The face of the cylinder to configure
+  /// @param loc0 The first binning configuration along local axis 0
+  /// @param loc1 The second binning configuration along local axis 1
+  /// @return The material designator node
+  /// @note If this node has previously been configured with a different volume
+  ///       shape, this will throw an exception.
+  MaterialDesignatorBlueprintNode& configureFace(
+      CylinderVolumeBounds::Face face, const ProtoAxis& loc0,
+      const ProtoAxis& loc1);
+
+  /// Configure the designator with a cuboid face and corresponding binning
+  /// information.
+  /// @note This method can be called multiple times to configure different faces.
+  /// @param face The face of the cuboid to configure
+  /// @param loc0 The first binning configuration along local axis 0
+  /// @param loc1 The second binning configuration along local axis 1
+  /// @return The material designator node
+  /// @note If this node has previously been configured with a different volume
+  ///       shape, this will throw an exception.
+  MaterialDesignatorBlueprintNode& configureFace(CuboidVolumeBounds::Face face,
+                                                 const ProtoAxis& loc0,
+                                                 const ProtoAxis& loc1);
 
  private:
   /// @copydoc BlueprintNode::addToGraphviz
@@ -91,6 +109,19 @@ class MaterialDesignatorBlueprintNode final : public BlueprintNode {
           std::tuple<CylinderPortalShell::Face, ProtoAxis, ProtoAxis>>& binning,
       const Logger& logger);
 
+  void handleCuboidBinning(
+      CuboidPortalShell& cuboidShell,
+      const std::vector<
+          std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>& binning,
+      const Logger& logger);
+
+  void validateCylinderFaceConfig(CylinderVolumeBounds::Face face,
+                                  const ProtoAxis& loc0, const ProtoAxis& loc1,
+                                  const Logger& logger = getDummyLogger());
+
+  void validateCuboidFaceConfig(const ProtoAxis& loc0, const ProtoAxis& loc1,
+                                const Logger& logger = getDummyLogger());
+
   std::string m_name{};
 
   std::optional<BinningConfig> m_binning{};
diff --git a/Core/src/Geometry/Blueprint.cpp b/Core/src/Geometry/Blueprint.cpp
index 9244dc9d2e6..3cea409e049 100644
--- a/Core/src/Geometry/Blueprint.cpp
+++ b/Core/src/Geometry/Blueprint.cpp
@@ -8,6 +8,7 @@
 
 #include "Acts/Geometry/Blueprint.hpp"
 
+#include "Acts/Geometry/CuboidPortalShell.hpp"
 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
 #include "Acts/Geometry/CylinderPortalShell.hpp"
 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
@@ -87,10 +88,12 @@ std::unique_ptr<TrackingGeometry> Blueprint::construct(
   ACTS_DEBUG(prefix() << "have top volume: " << ss.str() << "\n"
                       << topVolume.transform().matrix());
 
-  std::shared_ptr<VolumeBounds> worldBounds;
+  std::unique_ptr<TrackingVolume> world;
+  static const std::string worldName = "World";
 
   if (const auto *cyl = dynamic_cast<const CylinderVolumeBounds *>(&bounds);
       cyl != nullptr) {
+    ACTS_VERBOSE(prefix() << "Expanding cylinder bounds");
     using enum CylinderVolumeBounds::BoundValues;
 
     // Make a copy that we'll modify
@@ -113,29 +116,81 @@ std::unique_ptr<TrackingGeometry> Blueprint::construct(
         {eMaxR, newBounds->get(eMaxR) + rEnv[1]},
     });
 
-    worldBounds = std::move(newBounds);
+    ACTS_DEBUG(prefix() << "Applied envelope to cylinder: Z=" << zEnv[0]
+                        << ", Rmin=" << rEnv[0] << ", Rmax=" << rEnv[1]);
+
+    world = std::make_unique<TrackingVolume>(topVolume.transform(),
+                                             std::move(newBounds), worldName);
+
+    // Need one-sided portal shell that connects outwards to nullptr
+    SingleCylinderPortalShell worldShell{*world};
+    worldShell.applyToVolume();
 
   } else if (const auto *box =
                  dynamic_cast<const CuboidVolumeBounds *>(&bounds);
              box != nullptr) {
-    throw std::logic_error{"Not implemented"};
+    ACTS_VERBOSE(prefix() << "Expanding cuboid bounds");
+    // Make a copy that we'll modify
+    auto newBounds = std::make_shared<CuboidVolumeBounds>(*box);
+
+    // Get the current half lengths
+    double halfX = newBounds->get(CuboidVolumeBounds::eHalfLengthX);
+    double halfY = newBounds->get(CuboidVolumeBounds::eHalfLengthY);
+    double halfZ = newBounds->get(CuboidVolumeBounds::eHalfLengthZ);
+
+    // Apply envelope to each dimension
+    const auto &xEnv = m_cfg.envelope[AxisX];
+    const auto &yEnv = m_cfg.envelope[AxisY];
+    const auto &zEnv = m_cfg.envelope[AxisZ];
+
+    // Check if envelopes are symmetric for all dimensions
+    if (xEnv[0] != xEnv[1]) {
+      ACTS_ERROR(
+          prefix() << "Root node cuboid envelope for X must be symmetric");
+      throw std::logic_error(
+          "Root node cuboid envelope for X must be symmetric");
+    }
+
+    if (yEnv[0] != yEnv[1]) {
+      ACTS_ERROR(
+          prefix() << "Root node cuboid envelope for Y must be symmetric");
+      throw std::logic_error(
+          "Root node cuboid envelope for Y must be symmetric");
+    }
+
+    if (zEnv[0] != zEnv[1]) {
+      ACTS_ERROR(
+          prefix() << "Root node cuboid envelope for Z must be symmetric");
+      throw std::logic_error(
+          "Root node cuboid envelope for Z must be symmetric");
+    }
+
+    newBounds->set({
+        {CuboidVolumeBounds::eHalfLengthX, halfX + xEnv[0]},
+        {CuboidVolumeBounds::eHalfLengthY, halfY + yEnv[0]},
+        {CuboidVolumeBounds::eHalfLengthZ, halfZ + zEnv[0]},
+    });
+
+    ACTS_DEBUG(prefix() << "Applied envelope to cuboid: X=" << xEnv[0]
+                        << ", Y=" << yEnv[0] << ", Z=" << zEnv[0]);
+
+    world = std::make_unique<TrackingVolume>(topVolume.transform(),
+                                             std::move(newBounds), worldName);
+
+    // Need one-sided portal shell that connects outwards to nullptr
+    SingleCuboidPortalShell worldShell{*world};
+    worldShell.applyToVolume();
+
   } else {
     throw std::logic_error{"Unsupported volume bounds type"};
   }
 
-  ACTS_DEBUG(prefix() << "New root volume bounds are: " << *worldBounds);
-
-  auto world = std::make_unique<TrackingVolume>(
-      topVolume.transform(), std::move(worldBounds), "World");
+  ACTS_DEBUG(prefix() << "New root volume bounds are: "
+                      << world->volumeBounds());
 
-  // @TODO: This needs to become configurable
   world->setNavigationPolicy(
       options.defaultNavigationPolicyFactory->build(gctx, *world, logger));
 
-  // Need one-sided portal shell that connects outwards to nullptr
-  SingleCylinderPortalShell worldShell{*world};
-  worldShell.applyToVolume();
-
   auto &shell = child.connect(options, gctx, logger);
 
   shell.fill(*world);
diff --git a/Core/src/Geometry/CuboidPortalShell.cpp b/Core/src/Geometry/CuboidPortalShell.cpp
index 0b5429a6e9a..b50d92a8d7f 100644
--- a/Core/src/Geometry/CuboidPortalShell.cpp
+++ b/Core/src/Geometry/CuboidPortalShell.cpp
@@ -28,6 +28,7 @@
 namespace Acts {
 
 void CuboidPortalShell::fill(TrackingVolume& volume) {
+  using enum CuboidVolumeBounds::Face;
   for (Face face : {NegativeZFace, PositiveZFace, NegativeXFace, PositiveXFace,
                     NegativeYFace, PositiveYFace}) {
     const auto& portalAtFace = portalPtr(face);
@@ -40,8 +41,10 @@ void CuboidPortalShell::fill(TrackingVolume& volume) {
 
 SingleCuboidPortalShell::SingleCuboidPortalShell(TrackingVolume& volume)
     : m_volume{&volume} {
+  using enum CuboidVolumeBounds::Face;
   if (m_volume->volumeBounds().type() != VolumeBounds::BoundsType::eCuboid) {
-    throw std::invalid_argument("Invalid volume bounds type");
+    throw std::invalid_argument(
+        "CuboidPortalShell: Invalid volume bounds type");
   }
 
   const auto& bounds =
@@ -117,8 +120,8 @@ CuboidStackPortalShell::CuboidStackPortalShell(
     const GeometryContext& gctx, std::vector<CuboidPortalShell*> shells,
     AxisDirection direction, const Logger& logger)
     : m_direction(direction), m_shells{std::move(shells)} {
+  using enum CuboidVolumeBounds::Face;
   using enum AxisDirection;
-
   std::tie(m_frontFace, m_backFace, m_sideFaces) =
       CuboidVolumeBounds::facesFromAxisDirection(m_direction);
 
diff --git a/Core/src/Geometry/CuboidVolumeBounds.cpp b/Core/src/Geometry/CuboidVolumeBounds.cpp
index 09faa427452..8d9be4406b5 100644
--- a/Core/src/Geometry/CuboidVolumeBounds.cpp
+++ b/Core/src/Geometry/CuboidVolumeBounds.cpp
@@ -207,3 +207,25 @@ CuboidVolumeBounds::facesFromAxisDirection(AxisDirection direction) {
 }
 
 }  // namespace Acts
+
+// Define operator<< for CuboidVolumeBounds::Face outside the class
+std::ostream& operator<<(std::ostream& os,
+                         Acts::CuboidVolumeBounds::Face face) {
+  using enum Acts::CuboidVolumeBounds::Face;
+  switch (face) {
+    case NegativeXFace:
+      return os << "NegativeXFace";
+    case PositiveXFace:
+      return os << "PositiveXFace";
+    case NegativeYFace:
+      return os << "NegativeYFace";
+    case PositiveYFace:
+      return os << "PositiveYFace";
+    case NegativeZFace:
+      return os << "NegativeZFace";
+    case PositiveZFace:
+      return os << "PositiveZFace";
+    default:
+      return os << "UnknownFace";
+  }
+}
diff --git a/Core/src/Geometry/CylinderPortalShell.cpp b/Core/src/Geometry/CylinderPortalShell.cpp
index d2de4fbb21d..7349dd3e5a8 100644
--- a/Core/src/Geometry/CylinderPortalShell.cpp
+++ b/Core/src/Geometry/CylinderPortalShell.cpp
@@ -36,7 +36,8 @@ void CylinderPortalShell::fill(TrackingVolume& volume) {
 SingleCylinderPortalShell::SingleCylinderPortalShell(TrackingVolume& volume)
     : m_volume{&volume} {
   if (m_volume->volumeBounds().type() != VolumeBounds::BoundsType::eCylinder) {
-    throw std::invalid_argument("Invalid volume bounds type");
+    throw std::invalid_argument(
+        "CylinderPortalShell: Invalid volume bounds type");
   }
 
   const auto& bounds =
diff --git a/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp b/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp
index 81c1b776304..35d1a1a6fd5 100644
--- a/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp
+++ b/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp
@@ -8,10 +8,14 @@
 
 #include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp"
 
+#include "Acts/Definitions/Algebra.hpp"
+#include "Acts/Geometry/CuboidPortalShell.hpp"
+#include "Acts/Geometry/CuboidVolumeBounds.hpp"
 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
 #include "Acts/Geometry/Portal.hpp"
 #include "Acts/Material/ProtoSurfaceMaterial.hpp"
 #include "Acts/Surfaces/Surface.hpp"
+#include "Acts/Utilities/Enumerate.hpp"
 #include "Acts/Utilities/GraphViz.hpp"
 #include "Acts/Utilities/Helpers.hpp"
 #include "Acts/Utilities/ProtoAxis.hpp"
@@ -45,51 +49,113 @@ Volume& MaterialDesignatorBlueprintNode::build(const BlueprintOptions& options,
   return children().at(0).build(options, gctx, logger);
 }
 
-void MaterialDesignatorBlueprintNode::handleCylinderBinning(
+void MaterialDesignatorBlueprintNode::validateCylinderFaceConfig(
+    CylinderVolumeBounds::Face face, const ProtoAxis& loc0,
+    const ProtoAxis& loc1, const Logger& logger) {
+  using enum CylinderVolumeBounds::Face;
+  using enum AxisDirection;
+
+  // Check if we already have a different volume type configured
+  if (m_binning.has_value() &&
+      !std::holds_alternative<std::vector<
+          std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+          m_binning.value())) {
+    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
+    throw std::invalid_argument(
+        "Cannot mix volume types in material configuration");
+  }
+
+  // Validate axis directions based on face type
+  switch (face) {
+    case NegativeDisc:
+    case PositiveDisc:
+      if (loc0.getAxisDirection() != AxisR ||
+          loc1.getAxisDirection() != AxisPhi) {
+        ACTS_ERROR(prefix() << "Disc faces must use (r, phi) binning");
+        throw std::invalid_argument("Disc faces must use (r, phi) binning");
+      }
+      break;
+    case OuterCylinder:
+    case InnerCylinder:
+      if (loc0.getAxisDirection() != AxisRPhi ||
+          loc1.getAxisDirection() != AxisZ) {
+        ACTS_ERROR(prefix() << "Cylinder faces must use (phi, z) binning");
+        throw std::invalid_argument("Cylinder faces must use (phi, z) binning");
+      }
+      break;
+    case NegativePhiPlane:
+    case PositivePhiPlane:
+      ACTS_ERROR(prefix() << "Phi plane faces are not supported");
+      throw std::invalid_argument("Phi plane faces are not supported");
+      break;
+  }
+}
 
+void MaterialDesignatorBlueprintNode::handleCylinderBinning(
     CylinderPortalShell& cylShell,
     const std::vector<
-        std::tuple<CylinderPortalShell::Face, ProtoAxis, ProtoAxis>>& binning,
+        std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>& binning,
     const Logger& logger) {
   ACTS_DEBUG(prefix() << "Binning is set to compatible type");
   using enum CylinderVolumeBounds::Face;
 
-  for (auto& [face, loc0, loc1] : binning) {
-    if (face == OuterCylinder || face == InnerCylinder) {
-      if (loc0.getAxisDirection() != AxisDirection::AxisRPhi) {
-        ACTS_ERROR(prefix() << "Binning is not in RPhi");
-        throw std::runtime_error("Binning is not in RPhi");
-      }
-
-      if (loc1.getAxisDirection() != AxisDirection::AxisZ) {
-        ACTS_ERROR(prefix() << "Binning is not in Z");
-        throw std::runtime_error("Binning is not in Z");
-      }
+  for (const auto& [face, loc0, loc1] : binning) {
+    auto* portal = cylShell.portal(face);
+    if (portal == nullptr) {
+      ACTS_ERROR(prefix() << "Portal is nullptr");
+      throw std::runtime_error("Portal is nullptr");
     }
 
-    if (face == PositiveDisc || face == NegativeDisc) {
-      if (loc0.getAxisDirection() != AxisDirection::AxisR) {
-        ACTS_ERROR(prefix() << "Binning is not in R");
-        throw std::runtime_error("Binning is not in R");
-      }
-      if (loc1.getAxisDirection() != AxisDirection::AxisPhi) {
-        ACTS_ERROR(prefix() << "Binning is not in Phi");
-        throw std::runtime_error("Binning is not in Phi");
-      }
-    }
+    ACTS_DEBUG(prefix() << "Assigning material with binning: " << loc0 << ", "
+                        << loc1 << " to face " << face);
+
+    portal->surface().assignSurfaceMaterial(
+        std::make_shared<ProtoGridSurfaceMaterial>(std::vector{loc0, loc1}));
+  }
+}
+
+void MaterialDesignatorBlueprintNode::validateCuboidFaceConfig(
+    const ProtoAxis& loc0, const ProtoAxis& loc1, const Logger& logger) {
+  using enum CuboidVolumeBounds::Face;
+  using enum AxisDirection;
+
+  // Check if we already have a different volume type configured
+  if (m_binning.has_value() &&
+      !std::holds_alternative<std::vector<
+          std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+          m_binning.value())) {
+    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
+    throw std::invalid_argument(
+        "Cannot mix volume types in material configuration");
+  }
 
-    std::vector<ProtoAxis> desc{loc0, loc1};
-    ACTS_DEBUG(prefix() << "~> Assigning proto binning " << desc << " to face "
-                        << face);
+  // For cuboid faces, the valid axes are always X and Y
+  if (loc0.getAxisDirection() != AxisX || loc1.getAxisDirection() != AxisY) {
+    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
+    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
+  }
+}
 
-    auto material = std::make_shared<ProtoGridSurfaceMaterial>(std::move(desc));
+void MaterialDesignatorBlueprintNode::handleCuboidBinning(
+    CuboidPortalShell& cuboidShell,
+    const std::vector<
+        std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>& binning,
+    const Logger& logger) {
+  ACTS_DEBUG(prefix() << "Binning is set to compatible type");
+  using enum CuboidVolumeBounds::Face;
 
-    auto portal = cylShell.portal(face);
+  for (const auto& [face, loc0, loc1] : binning) {
+    auto* portal = cuboidShell.portal(face);
     if (portal == nullptr) {
       ACTS_ERROR(prefix() << "Portal is nullptr");
       throw std::runtime_error("Portal is nullptr");
     }
-    portal->surface().assignSurfaceMaterial(std::move(material));
+
+    ACTS_DEBUG(prefix() << "Assigning material with binning: " << loc0 << ", "
+                        << loc1 << " to face " << face);
+
+    portal->surface().assignSurfaceMaterial(
+        std::make_shared<ProtoGridSurfaceMaterial>(std::vector{loc0, loc1}));
   }
 }
 
@@ -114,22 +180,33 @@ PortalShellBase& MaterialDesignatorBlueprintNode::connect(
   ACTS_DEBUG(prefix() << "Received shell from child "
                       << children().at(0).name());
 
-  if (auto* cylShell = dynamic_cast<CylinderPortalShell*>(&shell)) {
-    ACTS_DEBUG(prefix() << "Connecting cylinder shell");
+  if (!m_binning.has_value()) {
+    ACTS_WARNING(
+        prefix() << "Material designator node has no assignment configured");
+    return shell;
+  }
 
-    if (const auto* binning = std::get_if<std::vector<
-            std::tuple<CylinderPortalShell::Face, ProtoAxis, ProtoAxis>>>(
+  if (auto* cylShell = dynamic_cast<CylinderPortalShell*>(&shell)) {
+    if (auto* cylBinning = std::get_if<std::vector<
+            std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
             &m_binning.value());
-        binning != nullptr) {
-      handleCylinderBinning(*cylShell, *binning, logger);
+        cylBinning != nullptr) {
+      handleCylinderBinning(*cylShell, *cylBinning, logger);
     } else {
       ACTS_ERROR(prefix() << "Binning is set to unknown type");
       throw std::runtime_error("Unknown binning type");
     }
-
-  }
-  // @TODO: Handle cuboid volume shell here
-  else {
+  } else if (auto* cuboidShell = dynamic_cast<CuboidPortalShell*>(&shell)) {
+    if (auto* cuboidBinning = std::get_if<std::vector<
+            std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+            &m_binning.value());
+        cuboidBinning != nullptr) {
+      handleCuboidBinning(*cuboidShell, *cuboidBinning, logger);
+    } else {
+      ACTS_ERROR(prefix() << "Binning is set to unknown type");
+      throw std::runtime_error("Unknown binning type");
+    }
+  } else {
     ACTS_ERROR(prefix() << "Shell is not supported");
     throw std::runtime_error("Shell is not supported");
   }
@@ -142,6 +219,9 @@ void MaterialDesignatorBlueprintNode::finalize(const BlueprintOptions& options,
                                                TrackingVolume& parent,
                                                const Logger& logger) {
   if (children().size() != 1) {
+    ACTS_ERROR(prefix() << "MaterialDesignatorBlueprintNode must have exactly "
+                           "one child, but has "
+                        << children().size());
     throw std::runtime_error(
         "MaterialDesignatorBlueprintNode must have exactly one child");
   }
@@ -155,12 +235,24 @@ void MaterialDesignatorBlueprintNode::addToGraphviz(std::ostream& os) const {
 
   std::stringstream ss;
   ss << "" + name() + "";
-  ss << "<br/><i>CylinderContainer</i>";
+  ss << "<br/><i>MaterialDesignator</i>";
 
   std::visit(
       overloaded{
           [&](const std::vector<std::tuple<CylinderPortalShell::Face, ProtoAxis,
                                            ProtoAxis>>& binning) {
+            ss << "<br/><i>Cylinder Binning</i>";
+            for (const auto& [face, loc0, loc1] : binning) {
+              ss << "<br/>" << face;
+              ss << ": " << loc0.getAxisDirection() << "="
+                 << loc0.getAxis().getNBins();
+              ss << ", " << loc1.getAxisDirection() << "="
+                 << loc1.getAxis().getNBins();
+            }
+          },
+          [&](const std::vector<std::tuple<CuboidVolumeBounds::Face, ProtoAxis,
+                                           ProtoAxis>>& binning) {
+            ss << "<br/><i>Cuboid Binning</i>";
             for (const auto& [face, loc0, loc1] : binning) {
               ss << "<br/>" << face;
               ss << ": " << loc0.getAxisDirection() << "="
@@ -178,14 +270,39 @@ void MaterialDesignatorBlueprintNode::addToGraphviz(std::ostream& os) const {
   BlueprintNode::addToGraphviz(os);
 }
 
-const std::optional<MaterialDesignatorBlueprintNode::BinningConfig>&
-MaterialDesignatorBlueprintNode::binning() const {
-  return m_binning;
+MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
+    CylinderVolumeBounds::Face face, const ProtoAxis& loc0,
+    const ProtoAxis& loc1) {
+  validateCylinderFaceConfig(face, loc0, loc1);
+
+  if (!m_binning.has_value()) {
+    m_binning = std::vector<
+        std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>{};
+  }
+
+  auto& binning = std::get<std::vector<
+      std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+      m_binning.value());
+  binning.emplace_back(face, loc0, loc1);
+
+  return *this;
 }
 
-MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::setBinning(
-    BinningConfig binning) {
-  m_binning = std::move(binning);
+MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
+    CuboidVolumeBounds::Face face, const ProtoAxis& loc0,
+    const ProtoAxis& loc1) {
+  validateCuboidFaceConfig(loc0, loc1);
+
+  if (!m_binning.has_value()) {
+    m_binning = std::vector<
+        std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>{};
+  }
+
+  auto& binning = std::get<
+      std::vector<std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+      m_binning.value());
+  binning.emplace_back(face, loc0, loc1);
+
   return *this;
 }
 
diff --git a/Core/src/Geometry/StaticBlueprintNode.cpp b/Core/src/Geometry/StaticBlueprintNode.cpp
index 96b9e885ea1..968effa3917 100644
--- a/Core/src/Geometry/StaticBlueprintNode.cpp
+++ b/Core/src/Geometry/StaticBlueprintNode.cpp
@@ -8,6 +8,7 @@
 
 #include "Acts/Geometry/StaticBlueprintNode.hpp"
 
+#include "Acts/Geometry/CuboidPortalShell.hpp"
 #include "Acts/Geometry/CylinderPortalShell.hpp"
 #include "Acts/Geometry/GeometryContext.hpp"
 #include "Acts/Geometry/VolumeBounds.hpp"
@@ -23,7 +24,7 @@ StaticBlueprintNode::StaticBlueprintNode(std::unique_ptr<TrackingVolume> volume)
 Volume& StaticBlueprintNode::build(const BlueprintOptions& options,
                                    const GeometryContext& gctx,
                                    const Logger& logger) {
-  ACTS_DEBUG(prefix() << "static build");
+  ACTS_DEBUG(prefix() << "Static build");
   if (!m_volume) {
     throw std::runtime_error("Volume is not built");
   }
@@ -60,7 +61,7 @@ PortalShellBase& StaticBlueprintNode::connect(const BlueprintOptions& options,
     m_shell = std::make_unique<SingleCylinderPortalShell>(*m_volume);
 
   } else if (type == VolumeBounds::eCuboid) {
-    throw std::logic_error("Cuboid is not implemented yet");
+    m_shell = std::make_unique<SingleCuboidPortalShell>(*m_volume);
 
   } else {
     throw std::logic_error("Volume type is not supported");
diff --git a/Examples/Python/src/Blueprint.cpp b/Examples/Python/src/Blueprint.cpp
index 7a965fd1a41..731c5c317f3 100644
--- a/Examples/Python/src/Blueprint.cpp
+++ b/Examples/Python/src/Blueprint.cpp
@@ -404,13 +404,20 @@ void addBlueprint(Context& ctx) {
       },
       py::arg("name"), py::arg("direction"));
 
-  auto matNode =
-      py::class_<MaterialDesignatorBlueprintNode, BlueprintNode,
-                 std::shared_ptr<MaterialDesignatorBlueprintNode>>(
-          m, "MaterialDesignatorBlueprintNode")
-          .def(py::init<const std::string&>(), "name"_a)
-          .def_property("binning", &MaterialDesignatorBlueprintNode::binning,
-                        &MaterialDesignatorBlueprintNode::setBinning);
+  auto matNode = py::class_<MaterialDesignatorBlueprintNode, BlueprintNode,
+                            std::shared_ptr<MaterialDesignatorBlueprintNode>>(
+                     m, "MaterialDesignatorBlueprintNode")
+                     .def(py::init<const std::string&>(), "name"_a)
+                     .def("configureFace",
+                          py::overload_cast<CylinderVolumeBounds::Face,
+                                            const ProtoAxis&, const ProtoAxis&>(
+                              &MaterialDesignatorBlueprintNode::configureFace),
+                          "face"_a, "loc0"_a, "loc1"_a)
+                     .def("configureFace",
+                          py::overload_cast<CuboidVolumeBounds::Face,
+                                            const ProtoAxis&, const ProtoAxis&>(
+                              &MaterialDesignatorBlueprintNode::configureFace),
+                          "face"_a, "loc0"_a, "loc1"_a);
 
   addContextManagerProtocol(matNode);
 
diff --git a/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp b/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp
index e14fce3977f..3145ff127ce 100644
--- a/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp
+++ b/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp
@@ -24,6 +24,7 @@
 #include "Acts/Geometry/TrackingVolume.hpp"
 #include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
 #include "Acts/Geometry/VolumeResizeStrategy.hpp"
+#include "Acts/Material/ProtoSurfaceMaterial.hpp"
 #include "Acts/Navigation/INavigationPolicy.hpp"
 #include "Acts/Navigation/NavigationStream.hpp"
 #include "Acts/Surfaces/RectangleBounds.hpp"
@@ -266,12 +267,14 @@ BOOST_AUTO_TEST_CASE(NodeApiTestContainers) {
 
   root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode&
                                               mat) {
-    ProtoAxis zBinning{AxisDirection::AxisZ, AxisBoundaryType::Bound, 20};
+    using enum AxisDirection;
+    using enum AxisBoundaryType;
+    using enum CylinderVolumeBounds::Face;
 
-    ProtoAxis rPhiBinning{AxisDirection::AxisRPhi, AxisBoundaryType::Bound, 20};
-
-    mat.setBinning(std::vector{std::tuple{
-        CylinderVolumeBounds::Face::OuterCylinder, rPhiBinning, zBinning}});
+    // Configure cylinder faces with proper binning
+    mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 20}, {AxisZ, Bound, 20});
+    mat.configureFace(NegativeDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 25});
+    mat.configureFace(PositiveDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 25});
 
     mat.addCylinderContainer("Detector", AxisDirection::AxisR, [&](auto& det) {
       det.addCylinderContainer("Pixel", AxisDirection::AxisZ, [&](auto& cyl) {
@@ -404,6 +407,37 @@ BOOST_AUTO_TEST_CASE(NodeApiTestContainers) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(NodeApiTestCuboid) {
+  Transform3 base{Transform3::Identity()};
+
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
+  auto root = std::make_unique<Blueprint>(cfg);
+
+  root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode&
+                                              mat) {
+    using enum AxisDirection;
+    using enum AxisBoundaryType;
+    using enum CuboidVolumeBounds::Face;
+
+    // Configure valid axis combinations for each face type
+    mat.configureFace(NegativeXFace, {AxisX, Bound, 20}, {AxisY, Bound, 20});
+    mat.configureFace(PositiveXFace, {AxisX, Bound, 20}, {AxisY, Bound, 20});
+    mat.configureFace(NegativeYFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
+    mat.configureFace(PositiveYFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
+    mat.configureFace(NegativeZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
+    mat.configureFace(PositiveZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
+
+    mat.addStaticVolume(
+        base, std::make_shared<CuboidVolumeBounds>(100_mm, 100_mm, 100_mm),
+        "TestVolume");
+  });
+
+  auto trackingGeometry = root->construct({}, gctx, *logger);
+  BOOST_REQUIRE(trackingGeometry);
+}
+
 BOOST_AUTO_TEST_SUITE_END();
 
 BOOST_AUTO_TEST_SUITE_END();
diff --git a/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp b/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp
index 5939624eda2..5126599519f 100644
--- a/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp
+++ b/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp
@@ -32,6 +32,7 @@
 #include "Acts/Utilities/ProtoAxis.hpp"
 
 #include <fstream>
+#include <memory>
 #include <stdexcept>
 #include <vector>
 
@@ -41,6 +42,7 @@ using Acts::Experimental::Blueprint;
 using Acts::Experimental::BlueprintNode;
 using Acts::Experimental::BlueprintOptions;
 using Acts::Experimental::LayerBlueprintNode;
+using Acts::Experimental::MaterialDesignatorBlueprintNode;
 using Acts::Experimental::StaticBlueprintNode;
 
 namespace Acts::Test {
@@ -494,13 +496,9 @@ BOOST_AUTO_TEST_CASE(Material) {
   using enum AxisBoundaryType;
 
   root.addMaterial("Material", [&](auto& mat) {
-    // @TODO: This API is not great
-    mat.setBinning(std::vector{
-        std::tuple{NegativeDisc, ProtoAxis{AxisR, Bound, 5},
-                   ProtoAxis{AxisPhi, Bound, 10}},
-        std::tuple{PositiveDisc, ProtoAxis{AxisR, Bound, 15},
-                   ProtoAxis{AxisPhi, Bound, 20}},
-    });
+    mat.configureFace(NegativeDisc, {AxisR, Bound, 5}, {AxisPhi, Bound, 10});
+    mat.configureFace(PositiveDisc, {AxisR, Bound, 15}, {AxisPhi, Bound, 20});
+    mat.configureFace(OuterCylinder, {AxisRPhi, Bound, 25}, {AxisZ, Bound, 30});
 
     mat.addStaticVolume(std::move(cyl));
   });
@@ -511,13 +509,20 @@ BOOST_AUTO_TEST_CASE(Material) {
   auto lookup = nameLookup(*trackingGeometry);
   auto& child = lookup("child");
 
-  const auto* negDisc = child.portals().at(0).surface().surfaceMaterial();
-  const auto* posDisc = child.portals().at(1).surface().surfaceMaterial();
+  // Check negative disc material
+  const auto* negDisc = child.portals()
+                            .at(static_cast<std::size_t>(NegativeDisc))
+                            .surface()
+                            .surfaceMaterial();
   BOOST_CHECK_NE(negDisc, nullptr);
-  BOOST_CHECK_NE(posDisc, nullptr);
-
   const auto& negDiscMat =
       dynamic_cast<const ProtoGridSurfaceMaterial&>(*negDisc);
+  // Check positive disc material
+  const auto* posDisc = child.portals()
+                            .at(static_cast<std::size_t>(PositiveDisc))
+                            .surface()
+                            .surfaceMaterial();
+  BOOST_CHECK_NE(posDisc, nullptr);
   const auto& posDiscMat =
       dynamic_cast<const ProtoGridSurfaceMaterial&>(*posDisc);
 
@@ -526,9 +531,205 @@ BOOST_AUTO_TEST_CASE(Material) {
   BOOST_CHECK_EQUAL(posDiscMat.binning().at(0).getAxis().getNBins(), 15);
   BOOST_CHECK_EQUAL(posDiscMat.binning().at(1).getAxis().getNBins(), 20);
 
-  for (std::size_t i = 2; i < child.portals().size(); i++) {
-    BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
-                      nullptr);
+  // Check outer cylinder material
+  const auto* outerCyl = child.portals()
+                             .at(static_cast<std::size_t>(OuterCylinder))
+                             .surface()
+                             .surfaceMaterial();
+  BOOST_CHECK_NE(outerCyl, nullptr);
+  const auto& outerCylMat =
+      dynamic_cast<const ProtoGridSurfaceMaterial&>(*outerCyl);
+  BOOST_CHECK_EQUAL(outerCylMat.binning().at(0).getAxis().getNBins(), 25);
+  BOOST_CHECK_EQUAL(outerCylMat.binning().at(1).getAxis().getNBins(), 30);
+
+  // Check that other faces have no material
+  for (std::size_t i = 0; i < child.portals().size(); i++) {
+    if (i != static_cast<std::size_t>(NegativeDisc) &&
+        i != static_cast<std::size_t>(PositiveDisc) &&
+        i != static_cast<std::size_t>(OuterCylinder)) {
+      BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
+                        nullptr);
+    }
+  }
+}
+
+BOOST_AUTO_TEST_CASE(MaterialInvalidAxisDirections) {
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
+  Blueprint root{cfg};
+
+  using enum AxisDirection;
+  using enum AxisBoundaryType;
+
+  // Test invalid axis direction combinations for cylinder faces
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CylinderVolumeBounds::Face::NegativeDisc,
+                             {AxisZ, Bound, 5}, {AxisPhi, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CylinderVolumeBounds::Face::OuterCylinder,
+                             {AxisR, Bound, 5}, {AxisR, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  // Test invalid axis direction combinations for cuboid faces
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CuboidVolumeBounds::Face::NegativeXFace,
+                             {AxisX, Bound, 5}, {AxisZ, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CuboidVolumeBounds::Face::PositiveYFace,
+                             {AxisY, Bound, 5}, {AxisX, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CuboidVolumeBounds::Face::NegativeZFace,
+                             {AxisZ, Bound, 5}, {AxisY, Bound, 10});
+                       }),
+      std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(MaterialMixedVolumeTypes) {
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
+  Blueprint root{cfg};
+
+  using enum AxisDirection;
+  using enum AxisBoundaryType;
+
+  // Configure for cylinder first, then try to add cuboid - should throw
+  BOOST_CHECK_THROW(
+      root.addMaterial(
+          "Material",
+          [&](auto& mat) {
+            mat.configureFace(CylinderVolumeBounds::Face::NegativeDisc,
+                              {AxisR, Bound, 5}, {AxisPhi, Bound, 10});
+            mat.configureFace(CuboidVolumeBounds::Face::NegativeXFace,
+                              {AxisX, Bound, 5}, {AxisY, Bound, 10});
+          }),
+      std::invalid_argument);
+
+  // Configure for cuboid first, then try to add cylinder - should throw
+  BOOST_CHECK_THROW(
+      root.addMaterial(
+          "Material",
+          [&](auto& mat) {
+            mat.configureFace(CuboidVolumeBounds::Face::NegativeXFace,
+                              {AxisX, Bound, 5}, {AxisY, Bound, 10});
+            mat.configureFace(CylinderVolumeBounds::Face::NegativeDisc,
+                              {AxisR, Bound, 5}, {AxisPhi, Bound, 10});
+          }),
+      std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(MaterialCuboid) {
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
+  Blueprint root{cfg};
+
+  using enum AxisDirection;
+  using enum AxisBoundaryType;
+  using enum CuboidVolumeBounds::Face;
+
+  double hlX = 30_mm;
+  double hlY = 40_mm;
+  double hlZ = 50_mm;
+  auto cuboidBounds = std::make_shared<CuboidVolumeBounds>(hlX, hlY, hlZ);
+  auto cuboid = std::make_unique<TrackingVolume>(Transform3::Identity(),
+                                                 cuboidBounds, "child");
+
+  auto mat = std::make_shared<MaterialDesignatorBlueprintNode>("Material");
+
+  // Configure material for different faces with different binning
+  mat->configureFace(NegativeXFace, {AxisX, Bound, 5}, {AxisY, Bound, 10});
+  mat->configureFace(PositiveXFace, {AxisX, Bound, 15}, {AxisY, Bound, 20});
+  mat->configureFace(NegativeYFace, {AxisX, Bound, 25}, {AxisY, Bound, 30});
+  mat->configureFace(PositiveYFace, {AxisX, Bound, 35}, {AxisY, Bound, 40});
+  mat->configureFace(NegativeZFace, {AxisX, Bound, 45}, {AxisY, Bound, 50});
+  mat->configureFace(PositiveZFace, {AxisX, Bound, 55}, {AxisY, Bound, 60});
+
+  mat->addChild(std::make_shared<StaticBlueprintNode>(std::move(cuboid)));
+
+  root.addChild(mat);
+
+  auto trackingGeometry =
+      root.construct({}, gctx, *logger->clone(std::nullopt, Logging::VERBOSE));
+
+  BOOST_REQUIRE(trackingGeometry);
+
+  auto lookup = nameLookup(*trackingGeometry);
+  auto& child = lookup("child");
+
+  // Check that material is attached to all faces
+  for (std::size_t i = 0; i < child.portals().size(); i++) {
+    const auto* material = child.portals().at(i).surface().surfaceMaterial();
+    BOOST_CHECK_NE(material, nullptr);
+
+    const auto& gridMaterial =
+        dynamic_cast<const ProtoGridSurfaceMaterial&>(*material);
+
+    // Check binning based on face
+    CuboidVolumeBounds::Face face = static_cast<CuboidVolumeBounds::Face>(i);
+    switch (face) {
+      case NegativeXFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(), 5);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          10);
+        break;
+      case PositiveXFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          15);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          20);
+        break;
+      case NegativeYFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          25);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          30);
+        break;
+      case PositiveYFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          35);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          40);
+        break;
+      case NegativeZFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          45);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          50);
+        break;
+      case PositiveZFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          55);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          60);
+        break;
+    }
   }
 }
 


Reviews:
user: login: paulgessinger
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-20T15:46:10Z
user: login: coderabbitai[bot]
body: > [!CAUTION]
> Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

**Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (1)</summary><blockquote>

`30-36`: **Variant usage, flexible it can be, yes.**  
Plan for more volume shapes, you should, lest this grows unwieldy.

</blockquote></details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (3)</summary><blockquote>

`51-91`: **Validate cylinder faces thoroughly, you do.**  
Refactor repeated checks if more faces or expansions appear, consider you might.

---

`169-179`: **Placeholder for cuboid handling, incomplete it is.**  
Offer assistance, I can. Implement soon, you must.

---

`195-197`: **Repeated child-check, see I do.**  
Extract common error message, you could.

</blockquote></details>

</blockquote></details>

<details>
<summary>🛑 Comments failed to post (1)</summary><blockquote>

<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (1)</summary><blockquote>

93-114: _:warning: Potential issue_

**Mismatch in error message, discovered we have.**  
Logic expects (x, y), but states (y, z). Fix with correct axis naming, you must.  

Proposed fix:
```diff
-    ACTS_ERROR(prefix() << "Cuboid faces must use (y, z) binning");
-    throw std::invalid_argument("Cuboid faces must use (y, z) binning");
+    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
+    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
void MaterialDesignatorBlueprintNode::validateCuboidFaceConfig(
    const Experimental::ProtoBinning& loc0,
    const Experimental::ProtoBinning& loc1, const Logger& logger) {
  using enum CuboidVolumeBounds::Face;
  using enum AxisDirection;

  // Check if we already have a different volume type configured
  if (m_binning.has_value() &&
      !std::holds_alternative<std::vector<
          std::tuple<CuboidVolumeBounds::Face, Experimental::ProtoBinning,
                     Experimental::ProtoBinning>>>(m_binning.value())) {
    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
    throw std::invalid_argument(
        "Cannot mix volume types in material configuration");
  }

  // There is no ambiguity as to which directions are accepted
  if (loc0.axisDir != AxisX || loc1.axisDir != AxisY) {
    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
  }
}
`````

</details>

<!-- suggestion_end -->

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-20T15:51:09Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (1)</summary><blockquote>

`28-36`: **Address the TODO comment, you must.**

Incomplete implementation, the TODO comment suggests. A plan for cuboid volume storage, we need.


Help implement the cuboid volume storage, shall I? Open a new issue to track this task, I can.

</blockquote></details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (2)</summary><blockquote>

`51-91`: **Refactor for cleaner validation flow, we should.**

Good validation logic, but cleaner it could be with early returns. Reduce nesting and improve readability, this would.

Apply this refactor, you should:

```diff
void MaterialDesignatorBlueprintNode::validateCylinderFaceConfig(
    CylinderVolumeBounds::Face face, const Experimental::ProtoBinning& loc0,
    const Experimental::ProtoBinning& loc1, const Logger& logger) {
  using enum CylinderVolumeBounds::Face;
  using enum AxisDirection;

  // Check if we already have a different volume type configured
  if (m_binning.has_value() &&
      !std::holds_alternative<std::vector<
          std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
                     Experimental::ProtoBinning>>>(m_binning.value())) {
    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
    throw std::invalid_argument(
        "Cannot mix volume types in material configuration");
  }

-  // Validate axis directions based on face type
-  switch (face) {
-    case NegativeDisc:
-    case PositiveDisc:
-      if (loc0.axisDir != AxisR || loc1.axisDir != AxisPhi) {
-        ACTS_ERROR(prefix() << "Disc faces must use (r, phi) binning");
-        throw std::invalid_argument("Disc faces must use (r, phi) binning");
-      }
-      break;
-    case OuterCylinder:
-    case InnerCylinder:
-      if (loc0.axisDir != AxisPhi || loc1.axisDir != AxisZ) {
-        ACTS_ERROR(prefix() << "Cylinder faces must use (phi, z) binning");
-        throw std::invalid_argument("Cylinder faces must use (phi, z) binning");
-      }
-      break;
-    case NegativePhiPlane:
-    case PositivePhiPlane:
-      if (loc0.axisDir != AxisR || loc1.axisDir != AxisZ) {
-        ACTS_ERROR(prefix() << "Phi plane faces must use (r, z) binning");
-        throw std::invalid_argument("Phi plane faces must use (r, z) binning");
-      }
-      break;
-  }
+  // Validate axis directions based on face type
+  if ((face == NegativeDisc || face == PositiveDisc) &&
+      (loc0.axisDir != AxisR || loc1.axisDir != AxisPhi)) {
+    ACTS_ERROR(prefix() << "Disc faces must use (r, phi) binning");
+    throw std::invalid_argument("Disc faces must use (r, phi) binning");
+  }
+
+  if ((face == OuterCylinder || face == InnerCylinder) &&
+      (loc0.axisDir != AxisPhi || loc1.axisDir != AxisZ)) {
+    ACTS_ERROR(prefix() << "Cylinder faces must use (phi, z) binning");
+    throw std::invalid_argument("Cylinder faces must use (phi, z) binning");
+  }
+
+  if ((face == NegativePhiPlane || face == PositivePhiPlane) &&
+      (loc0.axisDir != AxisR || loc1.axisDir != AxisZ)) {
+    ACTS_ERROR(prefix() << "Phi plane faces must use (r, z) binning");
+    throw std::invalid_argument("Phi plane faces must use (r, z) binning");
+  }
}
```

---

`93-117`: **Enhanced logging, add we should.**

Good implementation, but more detailed logging would help debugging. Add logging for portal nullptr case and successful material assignment.

Add these logging statements:

```diff
void MaterialDesignatorBlueprintNode::handleCylinderBinning(
    CylinderPortalShell& cylShell,
    const std::vector<
        std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
                   Experimental::ProtoBinning>>& binning,
    const Logger& logger) {
  ACTS_DEBUG(prefix() << "Binning is set to compatible type");
  using enum CylinderVolumeBounds::Face;

  for (const auto& [face, loc0, loc1] : binning) {
    auto* portal = cylShell.portal(face);
    if (portal == nullptr) {
+      ACTS_ERROR(prefix() << "Portal for face " << face << " is nullptr");
      throw std::runtime_error("Portal is nullptr");
    }

    Experimental::BinningDescription desc{.binning = {loc0, loc1}};

    ACTS_DEBUG(prefix() << "Assigning material with binning: "
                        << desc.toString() << " to face " << face);
+    ACTS_DEBUG(prefix() << "Material assignment successful for face " << face);
  }
}
```

</blockquote></details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (1)</summary><blockquote>

`405-458`: **More descriptive test case comments, add we should.**

Good test coverage, but clearer documentation would help future maintainers understand test scenarios better.

Add these descriptive comments:

```diff
BOOST_AUTO_TEST_CASE(NodeApiTestCuboid) {
+  // Test case verifies:
+  // 1. Valid cuboid face configurations are accepted
+  // 2. Invalid axis combinations are rejected
+  // 3. Cuboid support is not yet implemented
  Transform3 base{Transform3::Identity()};

  Blueprint::Config cfg;
  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
  cfg.envelope[AxisDirection::AxisR] = {0_mm, 20_mm};
  auto root = std::make_unique<Blueprint>(cfg);
```

</blockquote></details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (1)</summary><blockquote>

`547-602`: **Validate error messages in tests, we should.**

Good test coverage of invalid configurations, but verify error messages we must.

Add error message validation:

```diff
  BOOST_CHECK_THROW(
      root.addMaterial("Material",
                       [&](auto& mat) {
                         mat.configureFace(
                             CylinderVolumeBounds::Face::NegativeDisc,
                             {AxisZ, Bound, 5}, {AxisPhi, Bound, 10});
                       }),
+      std::invalid_argument,
+      "Disc faces must use (r, phi) binning");
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 77034a181d3bd71bb5e8af2aba47ccd16b7ce813 and d48abe2d5e42a7482a98b65598b4cac234660ef8.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `Core/include/Acts/Geometry/CuboidVolumeBounds.hpp` (2 hunks)
* `Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp` (4 hunks)
* `Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp` (5 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (2 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintTests.cpp` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Core/include/Acts/Geometry/CuboidVolumeBounds.hpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (10)</summary>

* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: merge-sentinel
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: macos
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: CI Bridge / clang_tidy

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (2)</summary>

`82-93`: **Well-documented and clear, this method is.**

Good practices in documentation and validation, I see. Multiple face configurations, it supports.

---

`95-106`: **Consistent with cylinder implementation, this method is.**

Mirror the cylinder configuration pattern, it does. Clear documentation and validation checks, it maintains.

</details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (1)</summary>

`233-269`: **Well-structured and consistent, these methods are.**

Follow good patterns of validation and configuration, they do. Handle errors appropriately, they also do.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (2)</summary>

`474-545`: **Well-structured material tests, these are.**

Good coverage of material configuration, they provide. Use new API correctly, they do.

---

`638-662`: **Clear and focused test case, this is.**

Verify unimplemented cuboid support correctly, it does. Good error checking, it has.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-20T15:54:27Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Examples/Python/src/Blueprint.cpp (1)</summary><blockquote>

`372-387`: **Approve the migration to overload-based API, I do!**

Wise changes these are, hmm! Type-safe configuration for both cylinder and cuboid faces, they provide. Clear and explicit interface, it creates.


**Benefits, I shall list:**
- Earlier error detection through type safety
- Clear separation between cylinder and cuboid configurations
- Explicit parameter naming with `face`, `loc0`, and `loc1`


A suggestion for future consideration, I have. Documentation strings for the Python bindings, add you should. Help users understand the parameters better, it will.

```diff
 .def("configureFace",
      py::overload_cast<CylinderVolumeBounds::Face,
                        const Experimental::ProtoBinning&,
                        const Experimental::ProtoBinning&>(
          &MaterialDesignatorBlueprintNode::configureFace),
-     "face"_a, "loc0"_a, "loc1"_a)
+     "face"_a, "loc0"_a, "loc1"_a,
+     "Configure material for a cylinder face. face: The face to configure, "
+     "loc0: First location binning, loc1: Second location binning")
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d48abe2d5e42a7482a98b65598b4cac234660ef8 and 499d0f4c29b2473150db294df3445f6fdfe081e4.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `Examples/Python/src/Blueprint.cpp` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (12)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: macos
* GitHub Check: missing_includes
* GitHub Check: build_debug

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-20T15:56:42Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (2)</summary><blockquote>

`117-138`: **Restrictive, this validation is.**

Enforce same (x, y) binning for all cuboid faces, you do. Consider face-specific binning requirements based on orientation, we should.

```diff
-  if (loc0.axisDir != AxisX || loc1.axisDir != AxisY) {
-    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
-    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
+  // Validate based on face orientation
+  switch (face) {
+    case NegativeXFace:
+    case PositiveXFace:
+      if (loc0.axisDir != AxisY || loc1.axisDir != AxisZ) {
+        throw std::invalid_argument("X faces must use (y, z) binning");
+      }
+      break;
+    case NegativeYFace:
+    case PositiveYFace:
+      if (loc0.axisDir != AxisX || loc1.axisDir != AxisZ) {
+        throw std::invalid_argument("Y faces must use (x, z) binning");
+      }
+      break;
+    case NegativeZFace:
+    case PositiveZFace:
+      if (loc0.axisDir != AxisX || loc1.axisDir != AxisY) {
+        throw std::invalid_argument("Z faces must use (x, y) binning");
+      }
+      break;
+  }
```

---

`231-248`: **Safe variant access, we must ensure.**

While correct the implementation is, add error handling for variant access, we should.

```diff
   auto& binning = std::get<std::vector<
       std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
                  Experimental::ProtoBinning>>>(m_binning.value());
+  if (!std::holds_alternative<std::vector<
+          std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
+                     Experimental::ProtoBinning>>>(m_binning.value())) {
+    throw std::runtime_error("Invalid binning type");
+  }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 499d0f4c29b2473150db294df3445f6fdfe081e4 and 095c8370f6ee961c9d58c1eb56c6c8f27ade6a80.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp` (4 hunks)
* `Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp` (5 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (2 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintTests.cpp` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (13)</summary>

* GitHub Check: CI Bridge / lcg_106a: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: CI Bridge / build_gnn_tensorrt
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: merge-sentinel

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (3)</summary>

`28-34`: **Wise choice, the BinningConfig type update is!**

Support for both cylinder and cuboid volume faces through std::variant, this type now provides. Clean and type-safe approach, this is.

---

`80-91`: **Well documented and designed, this method is!**

Clear documentation about multiple calls and volume type restrictions, you have provided. Strong in the Force, this API design is.

---

`93-104`: **Incomplete implementation, I sense.**

While well-designed the API is, implemented the cuboid support is not, as indicated by TODO on line 179. Ensure proper error handling until implementation complete, we must.

Consider adding runtime checks or compile-time constraints to prevent usage of unimplemented features.

</details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (1)</summary>

`51-89`: **Strong validation logic, you have built!**

Thorough checks for axis directions and volume type mixing, this implementation provides. Clear error messages, it gives. Help developers catch mistakes early, it will.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (1)</summary>

`405-458`: **Comprehensive test coverage, you have created!**

Test both valid configurations and error cases, you do. Verify current limitations explicitly, this test does. Help prevent accidental use of unimplemented features, it will.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (3)</summary>

`474-545`: **Well adapted to new API, these tests are!**

Clear and thorough testing of material configuration for cylinder faces, you provide. Good coverage of the new API, this gives.

---

`547-602`: **Strong in error testing, this case is!**

Thoroughly test invalid axis combinations, you do. Help catch configuration errors early, these tests will.

---

`638-662`: **Wise to test unimplemented features, it is!**

Verify proper error handling for cuboid support, this test does. Prevent silent failures, it will.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-21T08:53:32Z
user: login: ssdetlab
body: Looks good. I suggest to finally merge #4047 before proceeding with this, so that `connect()` and the tests could be expanded for the cuboids
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-23T20:19:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (2)</summary><blockquote>

`79-103`: **Document thread safety guarantees, you must.**

Clear and well-documented, these methods are. But silent on thread safety, they remain. Add documentation about concurrent access, you should.

Add thread safety documentation:

```diff
  /// @note If this node has previously been configured with a different volume
  ///       shape, this will throw an exception.
+ /// @note This method is not thread-safe and should not be called concurrently
+ ///       with other configuration methods.
  MaterialDesignatorBlueprintNode& configureFace(
```

---

`116-123`: **Consistency in validation methods, maintain we must.**

Different signatures between cylinder and cuboid validation methods, I see. Missing face parameter in cuboid validation, there is. Consistent interface, we should have.

Add face parameter to cuboid validation:

```diff
-  void validateCuboidFaceConfig(const Experimental::ProtoBinning& loc0,
-                                const Experimental::ProtoBinning& loc1,
-                                const Logger& logger = getDummyLogger());
+  void validateCuboidFaceConfig(CuboidVolumeBounds::Face face,
+                                const Experimental::ProtoBinning& loc0,
+                                const Experimental::ProtoBinning& loc1,
+                                const Logger& logger = getDummyLogger());
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 095c8370f6ee961c9d58c1eb56c6c8f27ade6a80 and fb92a2c4b4acaa7044b0c80116ad5bc799eee7c3.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `Core/include/Acts/Geometry/CuboidVolumeBounds.hpp` (2 hunks)
* `Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Core/include/Acts/Geometry/CuboidVolumeBounds.hpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (24)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: CI Bridge / lcg_107: [alma9, clang19]
* GitHub Check: CI Bridge / lcg_107: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_107: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_106a: [alma9, clang16]
* GitHub Check: unused_files
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc13]
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / build_gnn_tensorrt
* GitHub Check: macos
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, clang++)
* GitHub Check: missing_includes
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, g++)
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: linux_ubuntu
* GitHub Check: build_debug

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (1)</summary>

`13-13`: **Strong with the Force, these changes are!**

Wisely chosen, the variant type is. Both cylinder and cuboid configurations, it now supports. Clean and maintainable, the code remains.



Also applies to: 27-33

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-24T08:54:06Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 82d3b205b8c9fa6ffb75c2e585739f51ce91d254 and 8f5e5285b544926d2922b07312b63ff2ee8f1727.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp` (4 hunks)
* `Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp` (5 hunks)
* `Examples/Python/src/Blueprint.cpp` (1 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (2 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintTests.cpp` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (1)</summary>

```
Learnt from: paulgessinger
PR: acts-project/acts#3869
File: Core/include/Acts/Geometry/CylinderContainerBlueprintNode.hpp:28-29
Timestamp: 2024-12-05T20:21:24.253Z
Learning: In `CylinderContainerBlueprintNode`, validation that child volumes are cylinders is enforced by `CylinderVolumeStack`.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: unused_files
* GitHub Check: macos
* GitHub Check: missing_includes
* GitHub Check: linux_ubuntu
* GitHub Check: build_debug
* GitHub Check: docs

</details>

<details>
<summary>🔇 Additional comments (16)</summary><blockquote>

<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (1)</summary>

`409-462`: **Comprehensive test for cuboid face configurations, this is.**

Well structured test case for validating cuboid face configurations, I see. Both valid and invalid axis combinations you test, and verify that cuboid support is not yet implemented, you do. Good test coverage, hmm, yes.

</details>
<details>
<summary>Examples/Python/src/Blueprint.cpp (1)</summary>

`375-388`: **Clean migration to overload-based API, I approve.**

Replaced property-based approach with method-based configuration, you have. Multiple method overloads based on volume type, you now provide. Clearer API and better type safety, this creates. The Force is strong with this change.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (5)</summary>

`497-499`: **Properly updated to new API, this is.**

Replaced old binning setup with direct `configureFace` calls for cylinder faces, you have. Consistent with the API migration, this change is.

---

`510-551`: **Improved test structure and readability, I sense.**

Better organized test with clear section comments, this now is. Each face type checked separately, making tests more maintainable. Explicit verification that other faces have no material, wise addition this is.

---

`554-609`: **Thorough validation of axis constraints, this test provides.**

Test invalid axis combinations for both cylinder and cuboid faces, you do. Ensures robust error handling and prevents invalid configurations early, it does. Strong boundaries, essential for maintainable code they are.

---

`611-643`: **Wise test for volume type mixing limitations, this is.**

Verify that mixing volume types throws appropriate exceptions, you do. Prevent confusion and ensure consistent material configurations, this will. Good defensive programming practice, hmm, yes.

---

`645-669`: **Clear communication of unimplemented feature, this test provides.**

Test confirms that cuboid support is not yet implemented, it does. Throws appropriate exceptions when attempted, it does. Users informed early about limitations, they are. Better than failures during runtime, this is.

</details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (4)</summary>

`51-91`: **Well structured validation for cylinder faces, I approve.**

Thorough validation of axis directions based on face type, this provides. Ensures configuration errors caught early, not during building process. Prevent user confusion, this will.

---

`117-137`: **Incorrect error message in cuboid validation, I sense.**

The error message states "Cuboid faces must use (x, y) binning" which matches the validation check, but contradicts previous implementations where different axis combinations were accepted for different faces. Consider adding face-specific validation as done for cylinder faces.



```diff
-void MaterialDesignatorBlueprintNode::validateCuboidFaceConfig(
-    const ProtoAxis& loc0, const ProtoAxis& loc1, const Logger& logger) {
+void MaterialDesignatorBlueprintNode::validateCuboidFaceConfig(
+    CuboidVolumeBounds::Face face, const ProtoAxis& loc0, 
+    const ProtoAxis& loc1, const Logger& logger) {
  using enum CuboidVolumeBounds::Face;
  using enum AxisDirection;

  // Check if we already have a different volume type configured
  if (m_binning.has_value() &&
      !std::holds_alternative<std::vector<
          std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
          m_binning.value())) {
    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
    throw std::invalid_argument(
        "Cannot mix volume types in material configuration");
  }

-  // There is no ambiguity as to which directions are accepted
-  if (loc0.getAxisDirection() != AxisX || loc1.getAxisDirection() != AxisY) {
-    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
-    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
+  // Validate axis directions based on face type
+  switch (face) {
+    case NegativeXFace:
+    case PositiveXFace:
+      if (loc0.getAxisDirection() != AxisY || 
+          loc1.getAxisDirection() != AxisZ) {
+        ACTS_ERROR(prefix() << "X faces must use (y, z) binning");
+        throw std::invalid_argument("X faces must use (y, z) binning");
+      }
+      break;
+    case NegativeYFace:
+    case PositiveYFace:
+      if (loc0.getAxisDirection() != AxisX || 
+          loc1.getAxisDirection() != AxisZ) {
+        ACTS_ERROR(prefix() << "Y faces must use (x, z) binning");
+        throw std::invalid_argument("Y faces must use (x, z) binning");
+      }
+      break;
+    case NegativeZFace:
+    case PositiveZFace:
+      if (loc0.getAxisDirection() != AxisX || 
+          loc1.getAxisDirection() != AxisY) {
+        ACTS_ERROR(prefix() << "Z faces must use (x, y) binning");
+        throw std::invalid_argument("Z faces must use (x, y) binning");
+      }
+      break;
  }
}
```

---

`178-182`: **Incomplete feature, this is!**

Accept configurations we do, but handle them in connect method, we do not. Misleading to users, this can be.



```web
What is the best practice for handling partially implemented features in C++?
```

---

`231-247`: **Clean implementation of cylinder face configuration, I approve.**

Validate first, then store configuration in variant container. Chainable API with return of self reference, good practice this is. Strong type safety, this provides.

</details>
<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (5)</summary>

`12-12`: **Added, the CuboidVolumeBounds include is!**

Support for cuboid volumes, this brings. Necessary for the expanded functionality, it is.

---

`27-29`: **Enhanced type safety through variants, I sense!**

Hmm, yes! Both cylinder and cuboid configurations, this variant now holds. Improved type safety and clearer intentions, this change provides. Better diagnostics at compile time rather than runtime, you will have.

---

`75-87`: **For cylinders, a new configuration path there is!**

Clever API design, this is! Method chaining through reference return, good practice it follows. Documentation clear and helpful, it remains. Early errors thrown when incompatible volume shapes detected, better than failing during construction, this is!

---

`88-99`: **For cuboids too, configuration you now can!**

Consistent with cylinder API, this method is. Method overloading based on face type, the strength of C++ it uses. Good symmetry between cylinder and cuboid interfaces, I observe.

---

`111-116`: **Validation functions added, earlier errors caught they will be!**

Hmm, stronger validation, the path to reliable code it is! Detect configuration problems early, these private methods will. Default logger provided wisely, you have. Separate validation for each volume type, clean design this is!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-28T11:15:40Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>Core/src/Geometry/StaticBlueprintNode.cpp (1)</summary><blockquote>

`27-27`: **Small change in debug message, noticed I have.**

Capitalized first letter now is. Consistent with other debug messages, this makes it. Minor but good for consistency, yes.

</blockquote></details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (2)</summary><blockquote>

`183-187`: **Binning check repeated, slightly redundant**  
Logic overlap exists, but cautious design, I sense. Possibly unify, or keep for safety, you can.

---

`189-209`: **Dynamic casting for volume type, well-structured**  
Both cylinder and cuboid handled distinctly. If extended volume shapes appear, a new path easily can be added.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8f5e5285b544926d2922b07312b63ff2ee8f1727 and c8a2ed3cb0eb286c97de36af2ad68225b6e91a0c.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `Core/include/Acts/Geometry/CuboidPortalShell.hpp` (1 hunks)
* `Core/include/Acts/Geometry/CuboidVolumeBounds.hpp` (1 hunks)
* `Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp` (4 hunks)
* `Core/src/Geometry/Blueprint.cpp` (3 hunks)
* `Core/src/Geometry/CuboidPortalShell.cpp` (3 hunks)
* `Core/src/Geometry/CuboidVolumeBounds.cpp` (1 hunks)
* `Core/src/Geometry/CylinderPortalShell.cpp` (1 hunks)
* `Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp` (6 hunks)
* `Core/src/Geometry/StaticBlueprintNode.cpp` (3 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (3 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintTests.cpp` (3 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (26)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: CI Bridge / lcg_107: [alma9, clang19]
* GitHub Check: CI Bridge / lcg_107: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_107: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_106a: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc13]
* GitHub Check: unused_files
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / build_gnn_tensorrt
* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, clang++)
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: docs
* GitHub Check: macos
* GitHub Check: missing_includes
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, g++)
* GitHub Check: build_debug
* GitHub Check: linux_ubuntu

</details>

<details>
<summary>🔇 Additional comments (40)</summary><blockquote>

<details>
<summary>Core/include/Acts/Geometry/CuboidVolumeBounds.hpp (1)</summary>

`204-205`: **Stream operator added, I see. Hmm, a good addition this is!**

For printing and debugging, useful this operator will be. Each face of the cuboid volume, string representation now it has.

</details>
<details>
<summary>Core/src/Geometry/CylinderPortalShell.cpp (1)</summary>

`39-40`: **Improved error message, this is. Hmmmm, yes.**

More descriptive the error message now becomes. Identify the source of the error easier, developers will. Prefix with component name, a good practice this is.

</details>
<details>
<summary>Core/src/Geometry/StaticBlueprintNode.cpp (2)</summary>

`11-11`: **Include added for cuboid portal shell, I see. Good, good.**

Required this include is, for the implementation of cuboid volumes that follows. Proper order of includes maintained, yes.

---

`64-64`: **Support for cuboid volumes implemented now is! Important change this is!**

Instead of throwing error for cuboid volumes, now create a proper shell the code does. Progress this shows, hmm! Part of the migration to overload-based API mentioned in PR objectives, this change is.

</details>
<details>
<summary>Core/src/Geometry/CuboidVolumeBounds.cpp (1)</summary>

`211-231`: **Output stream operator implementation, well done this is!**

Complete implementation for the operator declared in header file, I see. All enum values properly handled are, with default case for safety included. Outside the class defined it is, as standard practice suggests. For debugging and logging, valuable this will be.


Namespace qualification added correctly:
```cpp
using enum Acts::CuboidVolumeBounds::Face;
```

Easy to maintain this will be, if new face types added are. Return the stream in each case, proper this is.

</details>
<details>
<summary>Core/src/Geometry/CuboidPortalShell.cpp (3)</summary>

`31-31`: **Helpful, the 'using enum' is, reduced qualifier clutter, yes.**  
Clarity, it brings.

---

`44-44`: **Consistency across constructors, I see.**  
Applying 'using enum' everywhere, good it is.

---

`123-124`: **Extended 'using enum' usage, hrrrm.**  
Ensure compiler’s C++20 support, you must, or trouble arise it will.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (3)</summary>

`27-27`: **New include, recognized for ProtoSurfaceMaterial usage.**  
Needed it is, correct.

---

`270-277`: **Enums used for cylinder faces, compact the code is.**  
Test coverage for these face configurations, thorough you must keep.

---

`410-463`: **Cuboid test case, strong coverage you add.**  
Properly verifying material assignment and volume presence, praiseworthy it is.

</details>
<details>
<summary>Core/src/Geometry/Blueprint.cpp (4)</summary>

`11-11`: **Including CuboidPortalShell, essential for expansions it is.**  
No mismatch in references, see I do.

---

`91-127`: **Unique pointer for 'world' volume, wise design.**  
Memory ownership clarity, improved it is.

---

`132-183`: **Cuboid envelope expansions, correct you have done.**  
Exceptions ensure invalid symmetry cannot pass, good practice it is.

---

`188-189`: **Debug message for final bounds, beneficial indeed.**  
Eases troubleshooting, it shall.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (9)</summary>

`497-499`: **Assert, correct usage your test shows**  
Disc and cylinder faces, configured properly they are. Binning alignment with your volume type, consistent. Good coverage, indeed.

---

`510-514`: **Material pointer checks, thorough these lines are**  
Yes, verifying negative disc material properly, you do. Helpful the test is, revealing missing references, it would.

---

`518-523`: **Positive disc material check, consistent**  
Similar approach, verifying correctness. Confirmed the pointer is not null, well done this is, hmmm.

---

`532-542`: **Outer cylinder material, you confirm**  
Yes, well-labeled face, correct binning bins set. Good test coverage, have you.

---

`543-550`: **Verify other faces remain empty**  
Wise approach, ensures no unintended material is assigned to unconfigured faces. Thorough your test is.

---

`554-609`: **Invalid axis directions test, comprehensive**  
Corner cases for both cylinder and cuboid, tested thoroughly they are. Guards misconfiguration, does it.

---

`611-643`: **Test mixing volume types, disallowed**  
Yes, preventing confusion you are. The code above enforces distinct shape usage, correct that is.

---

`645-730`: **MaterialCuboid test, good coverage**  
Check each face individually, verify bin counts. Thorough your approach is, confirming smooth operation, yes.

---

`732-780`: **Invalid axis combos for cuboid, tested well**  
Ensuring user cannot misconfigure axes, protective measures strong are. Found issues early, you will.

</details>
<details>
<summary>Core/include/Acts/Geometry/CuboidPortalShell.hpp (1)</summary>

`142-149`: **Explicit Face enumerations, consistent usage**  
Clarity improved, yes. The Face type fully qualified, it is. Good practice, well done.

</details>
<details>
<summary>Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp (11)</summary>

`11-13`: **Include expansions, beneficial they are**  
Add cuboid references, indeed. Overlaps or redundancy, see I do not.  



Also applies to: 18-18

---

`52-92`: **Validation logic for cylinder faces, robust**  
Preventing axis mismatch, the checks are. Kudos for explicit disc vs. cylinder handling.

---

`94-116`: **Cylinder binning assignment, well done**  
Null portal check the code has. Material creation correct, judge I do.

---

`117-137`: **Validation logic for cuboid faces, strong**  
Essential to forbid volume mixing and invalid axes, yes. Proper constraints, you enforce.

---

`139-160`: **Cuboid binning assignment, thorough approach**  
Portal must not be null, correct. Binning data, stored carefully it is.

---

`222-224`: **Validation of single child once more**  
Consistent with previous checks, no conflict here. Good to ensure a single child, yes.

---

`238-238`: **MaterialDesignator label for GraphViz**  
Helpful debugging info it provides. Clear to the user, it becomes.

---

`244-252`: **Cylinder binning visualization**  
Graphical clarity in logs you gain, beneficial it is.

---

`253-263`: **Cuboid binning visualization**  
Mirroring cylinder approach, consistent yes. Ease of debugging, improved it is.

---

`273-289`: **Cylinder configureFace method**  
Yes, a direct link to your validation logic. Ensures correct usage for each face, you do.

---

`291-307`: **Cuboid configureFace method**  
Proactive checks, consistent with your cylinder approach. Resilient design, I sense.

</details>
<details>
<summary>Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp (4)</summary>

`12-13`: **Cuboid includes, introduced here**  
Needed for new shape support, prudent move it is.

---

`28-30`: **BinningConfig variant extended**  
Allow cylinder or cuboid, flexible design. Future expansions, simpler it makes.

---

`76-88`: **Cylinder face configuration docs**  
Clear usage, guiding the user well. Example, you give.

---

`89-100`: **Cuboid face configuration docs**  
Parallel to cylinder docs, consistent approach a developer sees. A wise layout, yes.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-28T18:49:02Z
user: login: ssdetlab
body: Overall good. I'm mostly doubtful about the repeating test cases between the `BlueprintNodeApi` and the `Blueprint`, which may be a bit of an overkill. Am I missing something here?   
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T22:39:23Z
user: login: paulgessinger
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T10:06:40Z
user: login: paulgessinger
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T10:09:56Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (1)</summary><blockquote>

`410-435`: **New test case for cuboid volumes, well implemented it is.**

Test the new cuboid volume support, this case does. Sets up blueprint configuration and validates material face configuration, it does. Hmm, but more verification could be added.

Enhance the test with additional assertions for the material configuration, you could. Currently, only existence of tracking geometry, you check. Like this:

```diff
    mat.configureFace(NegativeZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
    mat.configureFace(PositiveZFace, {AxisX, Bound, 15}, {AxisY, Bound, 25});
  });

  auto trackingGeometry = root->construct({}, gctx, *logger);
  BOOST_REQUIRE(trackingGeometry);
+
+  // Verify material configuration on cuboid faces
+  auto lookup = nameLookup(*trackingGeometry);
+  auto& world = lookup("GlobalMaterial");
+  BOOST_CHECK_EQUAL(world.portals().size(), 6); // Six faces of a cuboid
```

</blockquote></details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (1)</summary><blockquote>

`647-734`: **Cuboid material test case, thorough but repetitive it is.**

Test case verifies material configuration for all six cuboid faces with different binning values. All aspects of the new cuboid support, it checks.

Repetitive switch statement, you have. Refactor to use data-driven approach, you could:

```diff
-    // Check binning based on face
-    CuboidVolumeBounds::Face face = static_cast<CuboidVolumeBounds::Face>(i);
-    switch (face) {
-      case NegativeXFace:
-        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(), 5);
-        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
-                          10);
-        break;
-      case PositiveXFace:
-        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
-                          15);
// ... more cases
+    // Expected binning values for each face
+    const std::array<std::pair<int, int>, 6> expectedBins = {{
+      {5, 10},   // NegativeXFace
+      {15, 20},  // PositiveXFace
+      {25, 30},  // NegativeYFace
+      {35, 40},  // PositiveYFace
+      {45, 50},  // NegativeZFace
+      {55, 60}   // PositiveZFace
+    }};
+    
+    BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(), 
+                       expectedBins[i].first);
+    BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                       expectedBins[i].second);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c8a2ed3cb0eb286c97de36af2ad68225b6e91a0c and 0119ee6d7d04e7755ee1813c59c19196010a574c.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (3 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintTests.cpp` (5 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (19)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: missing_includes
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, clang++)
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: unused_files
* GitHub Check: macos
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, g++)
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: linux_ubuntu
* GitHub Check: build_debug

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (3)</summary>

`27-27`: **New include for ProtoSurfaceMaterial, appropriate this is.**

Include properly added, it is, supporting the material configuration in tests.

---

`270-272`: **Good use of 'using enum' statements, mmm, yes.**

Improve readability, these statements do. Reduce code verbosity, they will. Modern C++20 feature, this is.

---

`275-277`: **Migrated to new overload-based API, this code has.**

Replace old binning approach with direct `configureFace` calls, you did. More expressive and less error-prone, this approach is.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (5)</summary>

`499-501`: **Updated material face configuration with new API, you have.**

Replace old binning methods with new `configureFace` calls, these lines do. More explicit about which face is being configured, this approach is.

---

`512-543`: **Verification for cylinder face material, improved it is.**

Test that material is correctly applied to specific cylinder faces, these assertions do. Clear and targeted verification, this is.

---

`546-553`: **Check for absence of material on unconfigured faces, thorough this is.**

Ensure that only explicitly configured faces have material, this code does. Good defensive test, mmm, yes.

---

`556-611`: **Test cases for invalid axis combinations, comprehensive they are.**

Test both cylinder and cuboid volume invalid configurations, this does. Verify error handling properly implemented, it does. Throw expected exceptions when invalid combinations provided, it should.

---

`613-645`: **Test for mixed volume types, important this is.**

Ensure that mixing cylinder and cuboid configurations properly rejected, this test does. Type safety it enforces, yes.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T10:18:42Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (1)</summary><blockquote>

`410-439`: **Good cuboid test case, this is! Hmm!**

Created a new test for cuboid configurations, you have. Proper it is, testing all six faces of cuboid with appropriate axis binning. Yes, hmmm.

Validate more properties of the constructed geometry, you could. Only existence checked it is. For thorough testing, inspect the binning configuration or material properties, you might.


More assertions add, you could:

```diff
  auto trackingGeometry = root->construct({}, gctx, *logger);
  BOOST_REQUIRE(trackingGeometry);
+ // Get the world volume and verify its properties
+ const TrackingVolume* world = trackingGeometry->highestTrackingVolume();
+ BOOST_REQUIRE(world);
+ BOOST_REQUIRE_EQUAL(world->volumeName(), "World");
+ // Verify the TestVolume exists inside
+ bool foundTestVolume = false;
+ world->visitVolumes([&foundTestVolume](const TrackingVolume* volume) {
+   if (volume->volumeName() == "TestVolume") {
+     foundTestVolume = true;
+   }
+ });
+ BOOST_REQUIRE(foundTestVolume);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0119ee6d7d04e7755ee1813c59c19196010a574c and 4629ebb9682bfcc7ea8892695c3c66745d991295.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `Core/src/Geometry/Blueprint.cpp` (3 hunks)
* `Core/src/Geometry/CuboidPortalShell.cpp` (3 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (3 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Core/src/Geometry/CuboidPortalShell.cpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, clang++)
* GitHub Check: macos
* GitHub Check: missing_includes
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, g++)
* GitHub Check: build_debug
* GitHub Check: linux_ubuntu

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (3)</summary>

`27-27`: **Include for ProtoSurfaceMaterial, a good addition it is!**

Material configurations for faces this header supports. Correctly added, it is.

---

`270-278`: **Well structured, these cylinder face configurations are!**

Clear and readable, the enum usage is. Properly configured all cylinder faces are, with appropriate binning parameters. The migration to overload-based API, successful it is.

---

`422-431`: **Properly configured all cuboid faces are, yes!**

Face configurations cover all six faces of the cuboid they do, with appropriate axis binning. Axis parameters well chosen they are, different for different face types. Good work, this is!

</details>
<details>
<summary>Core/src/Geometry/Blueprint.cpp (6)</summary>

`11-11`: **Added the CuboidPortalShell include, wisely you have!**

Required for the implementation of cuboid support, this include is. Good practice to include only what needed is, yes.

---

`91-93`: **Better ownership semantics, this change provides!**

From shared pointer to unique pointer, moved you have. Exclusive ownership of world volume, this represents. Static constant for world name, a good practice it is, hmm.

---

`119-128`: **Improved debug logging and volume creation, I see!**

More informative debug message with envelope values, helpful it is. Direct creation of TrackingVolume with proper name, cleaner approach this is. Single-sided portal shell correctly applied, essential for navigation it is!

---

`132-183`: **Comprehensive cuboid implementation, well done it is!**

Thorough error checking for symmetry in all dimensions, implemented you have. Proper extraction and application of envelope values, correct it is. Single-sided portal shell for cuboid, important for navigation it is.

Consistent with cylinder implementation, this code is. Debug logging provides clarity, yes.

---

`147-166`: **Symmetric envelope checks, crucial they are!**

For proper navigation, symmetry in world envelope dimensions required is. Clear error messages provided you have, helpful for users they will be.

---

`188-190`: **More descriptive debug output, appreciate I do!**

Log the final bounds directly from volume, more readable it is. Help with debugging issues in future, this will.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T15:58:08Z
user: login: ssdetlab
body: Should be good to go
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T06:01:53Z
user: login: ssdetlab
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:39:06Z

Review comments:
diff_hunk: @@ -26,9 +27,13 @@ class MaterialDesignatorBlueprintNode final : public BlueprintNode {
  public:
   // @TODO: This needs cuboid volume storage as well
path: Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp
user: login: paulgessinger
body: ```suggestion
```
created_at: 2025-02-20T15:46:10Z
author_association: MEMBER
diff_hunk: @@ -45,51 +48,94 @@ Volume& MaterialDesignatorBlueprintNode::build(const BlueprintOptions& options,
   return children().at(0).build(options, gctx, logger);
 }
 
+void MaterialDesignatorBlueprintNode::validateCylinderFaceConfig(
+    CylinderVolumeBounds::Face face, const Experimental::ProtoBinning& loc0,
+    const Experimental::ProtoBinning& loc1, const Logger& logger) {
+  using enum CylinderVolumeBounds::Face;
+  using enum AxisDirection;
+
+  // Check if we already have a different volume type configured
+  if (m_binning.has_value() &&
+      !std::holds_alternative<std::vector<
+          std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
+                     Experimental::ProtoBinning>>>(m_binning.value())) {
+    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
+    throw std::invalid_argument(
+        "Cannot mix volume types in material configuration");
+  }
+
+  // Validate axis directions based on face type
+  switch (face) {
+    case NegativeDisc:
+    case PositiveDisc:
+      if (loc0.axisDir != AxisR || loc1.axisDir != AxisPhi) {
+        ACTS_ERROR(prefix() << "Disc faces must use (r, phi) binning");
+        throw std::invalid_argument("Disc faces must use (r, phi) binning");
+      }
+      break;
+    case OuterCylinder:
+    case InnerCylinder:
+      if (loc0.axisDir != AxisPhi || loc1.axisDir != AxisZ) {
+        ACTS_ERROR(prefix() << "Cylinder faces must use (phi, z) binning");
+        throw std::invalid_argument("Cylinder faces must use (phi, z) binning");
+      }
+      break;
+    case NegativePhiPlane:
+    case PositivePhiPlane:
+      if (loc0.axisDir != AxisR || loc1.axisDir != AxisZ) {
+        ACTS_ERROR(prefix() << "Phi plane faces must use (r, z) binning");
+        throw std::invalid_argument("Phi plane faces must use (r, z) binning");
+      }
+      break;
+  }
+}
+
 void MaterialDesignatorBlueprintNode::handleCylinderBinning(
     CylinderPortalShell& cylShell,
     const std::vector<
-        std::tuple<CylinderPortalShell::Face, Experimental::ProtoBinning,
+        std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
                    Experimental::ProtoBinning>>& binning,
     const Logger& logger) {
   ACTS_DEBUG(prefix() << "Binning is set to compatible type");
   using enum CylinderVolumeBounds::Face;
 
-  for (auto& [face, loc0, loc1] : binning) {
-    if (face == OuterCylinder || face == InnerCylinder) {
-      if (loc0.axisDir != AxisDirection::AxisRPhi) {
-        ACTS_ERROR(prefix() << "Binning is not in RPhi");
-        throw std::runtime_error("Binning is not in RPhi");
-      }
-
-      if (loc1.axisDir != AxisDirection::AxisZ) {
-        ACTS_ERROR(prefix() << "Binning is not in Z");
-        throw std::runtime_error("Binning is not in Z");
-      }
-    }
-
-    if (face == PositiveDisc || face == NegativeDisc) {
-      if (loc0.axisDir != AxisDirection::AxisR) {
-        ACTS_ERROR(prefix() << "Binning is not in R");
-        throw std::runtime_error("Binning is not in R");
-      }
-      if (loc1.axisDir != AxisDirection::AxisPhi) {
-        ACTS_ERROR(prefix() << "Binning is not in Phi");
-        throw std::runtime_error("Binning is not in Phi");
-      }
+  for (const auto& [face, loc0, loc1] : binning) {
+    auto* portal = cylShell.portal(face);
+    if (portal == nullptr) {
+      ACTS_ERROR(prefix() << "Portal is nullptr");
+      throw std::runtime_error("Portal is nullptr");
     }
 
     Experimental::BinningDescription desc{.binning = {loc0, loc1}};
-    ACTS_DEBUG(prefix() << "~> Assigning proto binning " << desc.toString()
-                        << " to face " << face);
 
-    auto material = std::make_shared<ProtoGridSurfaceMaterial>(std::move(desc));
+    ACTS_DEBUG(prefix() << "Assigning material with binning: "
+                        << desc.toString() << " to face " << face);
 
-    auto portal = cylShell.portal(face);
-    if (portal == nullptr) {
-      ACTS_ERROR(prefix() << "Portal is nullptr");
-      throw std::runtime_error("Portal is nullptr");
-    }
-    portal->surface().assignSurfaceMaterial(std::move(material));
+    portal->surface().assignSurfaceMaterial(
+        std::make_shared<ProtoGridSurfaceMaterial>(desc));
+  }
+}
+
+void MaterialDesignatorBlueprintNode::validateCuboidFaceConfig(
+    const Experimental::ProtoBinning& loc0,
+    const Experimental::ProtoBinning& loc1, const Logger& logger) {
+  using enum CuboidVolumeBounds::Face;
+  using enum AxisDirection;
+
+  // Check if we already have a different volume type configured
+  if (m_binning.has_value() &&
+      !std::holds_alternative<std::vector<
+          std::tuple<CuboidVolumeBounds::Face, Experimental::ProtoBinning,
+                     Experimental::ProtoBinning>>>(m_binning.value())) {
+    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
+    throw std::invalid_argument(
+        "Cannot mix volume types in material configuration");
+  }
+
+  // There is no ambiguity as to which directions are accepted
+  if (loc0.axisDir != AxisX || loc1.axisDir != AxisY) {
+    ACTS_ERROR(prefix() << "Cuboid faces must use (y, z) binning");
+    throw std::invalid_argument("Cuboid faces must use (y, z) binning");
   }
 }
path: Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp
user: login: coderabbitai[bot]
body: _:warning: Potential issue_

**Incorrect error message in cuboid validation, I sense.**

The error message states "(y, z) binning" but checks for (x, y) binning. Confusing to users, this discrepancy is.

Fix the error message:

```diff
  if (loc0.axisDir != AxisX || loc1.axisDir != AxisY) {
-    ACTS_ERROR(prefix() << "Cuboid faces must use (y, z) binning");
-    throw std::invalid_argument("Cuboid faces must use (y, z) binning");
+    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
+    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
  }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
void MaterialDesignatorBlueprintNode::validateCuboidFaceConfig(
    const Experimental::ProtoBinning& loc0,
    const Experimental::ProtoBinning& loc1, const Logger& logger) {
  using enum CuboidVolumeBounds::Face;
  using enum AxisDirection;

  // Check if we already have a different volume type configured
  if (m_binning.has_value() &&
      !std::holds_alternative<std::vector<
          std::tuple<CuboidVolumeBounds::Face, Experimental::ProtoBinning,
                     Experimental::ProtoBinning>>>(m_binning.value())) {
    ACTS_ERROR(prefix() << "Cannot mix volume types in material configuration");
    throw std::invalid_argument(
        "Cannot mix volume types in material configuration");
  }

  // There is no ambiguity as to which directions are accepted
  if (loc0.axisDir != AxisX || loc1.axisDir != AxisY) {
    ACTS_ERROR(prefix() << "Cuboid faces must use (x, y) binning");
    throw std::invalid_argument("Cuboid faces must use (x, y) binning");
  }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-02-20T15:54:26Z
author_association: NONE
diff_hunk: @@ -177,14 +228,41 @@ void MaterialDesignatorBlueprintNode::addToGraphviz(std::ostream& os) const {
   BlueprintNode::addToGraphviz(os);
 }
 
-const std::optional<MaterialDesignatorBlueprintNode::BinningConfig>&
-MaterialDesignatorBlueprintNode::binning() const {
-  return m_binning;
+MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
+    CylinderVolumeBounds::Face face, const Experimental::ProtoBinning& loc0,
+    const Experimental::ProtoBinning& loc1) {
+  validateCylinderFaceConfig(face, loc0, loc1);
+
+  if (!m_binning.has_value()) {
+    m_binning = std::vector<
+        std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
+                   Experimental::ProtoBinning>>{};
+  }
+
+  auto& binning = std::get<std::vector<
+      std::tuple<CylinderVolumeBounds::Face, Experimental::ProtoBinning,
+                 Experimental::ProtoBinning>>>(m_binning.value());
+  binning.emplace_back(face, loc0, loc1);
+
+  return *this;
 }
 
-MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::setBinning(
-    BinningConfig binning) {
-  m_binning = std::move(binning);
+MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
+    CuboidVolumeBounds::Face face, const Experimental::ProtoBinning& loc0,
+    const Experimental::ProtoBinning& loc1) {
+  validateCuboidFaceConfig(loc0, loc1);
+
+  if (!m_binning.has_value()) {
+    m_binning = std::vector<
+        std::tuple<CuboidVolumeBounds::Face, Experimental::ProtoBinning,
+                   Experimental::ProtoBinning>>{};
+  }
+
+  auto& binning = std::get<std::vector<
+      std::tuple<CuboidVolumeBounds::Face, Experimental::ProtoBinning,
+                 Experimental::ProtoBinning>>>(m_binning.value());
+  binning.emplace_back(face, loc0, loc1);
+
   return *this;
 }
path: Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp
user: login: coderabbitai[bot]
body: _:warning: Potential issue_

**Incomplete feature, this is!**

Accept configurations we do, but handle them in connect method, we do not. Misleading to users, this can be.

Either implement cuboid support in connect method or prevent configuration of cuboid faces until support is added.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-02-21T08:53:30Z
author_association: NONE
diff_hunk: @@ -58,6 +59,22 @@ class CuboidVolumeBounds : public VolumeBounds {
     eSize
   };
 
+  /// Enum describing the possible faces of a cuboid volume
+  /// @note These values are synchronized with the BoundarySurfaceFace enum.
+  enum Face {
path: Core/include/Acts/Geometry/CuboidVolumeBounds.hpp
user: login: ssdetlab
body: ```suggestion
  enum class Face : unsigned int {
```
created_at: 2025-02-23T18:55:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,11 +25,13 @@ namespace Acts {
 /// @note This currently only supports a cylinder volume child
path: Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp
user: login: ssdetlab
body: ```suggestion
```
created_at: 2025-02-23T18:56:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,14 +228,39 @@ void MaterialDesignatorBlueprintNode::addToGraphviz(std::ostream& os) const {
   BlueprintNode::addToGraphviz(os);
 }
 
-const std::optional<MaterialDesignatorBlueprintNode::BinningConfig>&
-MaterialDesignatorBlueprintNode::binning() const {
-  return m_binning;
+MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
+    CylinderVolumeBounds::Face face, const ProtoAxis& loc0,
+    const ProtoAxis& loc1) {
+  validateCylinderFaceConfig(face, loc0, loc1);
+
+  if (!m_binning.has_value()) {
+    m_binning = std::vector<
+        std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>{};
+  }
+
+  auto& binning = std::get<std::vector<
+      std::tuple<CylinderVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+      m_binning.value());
+  binning.emplace_back(face, loc0, loc1);
+
+  return *this;
 }
 
-MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::setBinning(
-    BinningConfig binning) {
-  m_binning = std::move(binning);
+MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
+    CuboidVolumeBounds::Face face, const ProtoAxis& loc0,
+    const ProtoAxis& loc1) {
+  validateCuboidFaceConfig(loc0, loc1);
+
+  if (!m_binning.has_value()) {
+    m_binning = std::vector<
+        std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>{};
+  }
+
+  auto& binning = std::get<
+      std::vector<std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
+      m_binning.value());
+  binning.emplace_back(face, loc0, loc1);
+
   return *this;
 }
path: Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Similar implementation for cuboid faces, consistency I see.**

Same pattern as cylinder implementation, good for maintainability this is. But missing face parameter in validation call, fixed it must be.


```diff
MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
    CuboidVolumeBounds::Face face, const ProtoAxis& loc0,
    const ProtoAxis& loc1) {
-  validateCuboidFaceConfig(loc0, loc1);
+  validateCuboidFaceConfig(face, loc0, loc1);

  if (!m_binning.has_value()) {
    m_binning = std::vector<
        std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>{};
  }

  auto& binning = std::get<
      std::vector<std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
      m_binning.value());
  binning.emplace_back(face, loc0, loc1);

  return *this;
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::configureFace(
    CuboidVolumeBounds::Face face, const ProtoAxis& loc0,
    const ProtoAxis& loc1) {
  validateCuboidFaceConfig(face, loc0, loc1);

  if (!m_binning.has_value()) {
    m_binning = std::vector<
        std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>{};
  }

  auto& binning = std::get<
      std::vector<std::tuple<CuboidVolumeBounds::Face, ProtoAxis, ProtoAxis>>>(
      m_binning.value());
  binning.emplace_back(face, loc0, loc1);

  return *this;
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-02-28T11:15:40Z
author_association: NONE
diff_hunk: @@ -113,29 +116,81 @@ std::unique_ptr<TrackingGeometry> Blueprint::construct(
         {eMaxR, newBounds->get(eMaxR) + rEnv[1]},
     });
 
-    worldBounds = std::move(newBounds);
+    ACTS_DEBUG(prefix() << "Applied envelope to cylinder: Z=" << zEnv[0]
+                        << ", Rmin=" << rEnv[0] << ", Rmax=" << rEnv[1]);
+
+    world = std::make_unique<TrackingVolume>(topVolume.transform(),
+                                             std::move(newBounds), worldName);
+
+    // Need one-sided portal shell that connects outwards to nullptr
+    SingleCylinderPortalShell worldShell{*world};
+    worldShell.applyToVolume();
 
   } else if (const auto *box =
                  dynamic_cast<const CuboidVolumeBounds *>(&bounds);
              box != nullptr) {
-    throw std::logic_error{"Not implemented"};
+    ACTS_VERBOSE(prefix() << "Expanding cuboid bounds");
+    // Make a copy that we'll modify
+    auto newBounds = std::make_shared<CuboidVolumeBounds>(*box);
+
+    // Get the current half lengths
+    double halfX = newBounds->get(CuboidVolumeBounds::eHalfLengthX);
+    double halfY = newBounds->get(CuboidVolumeBounds::eHalfLengthY);
+    double halfZ = newBounds->get(CuboidVolumeBounds::eHalfLengthZ);
+
+    // Apply envelope to each dimension
+    const auto &xEnv = m_cfg.envelope[AxisX];
path: Core/src/Geometry/Blueprint.cpp
user: login: ssdetlab
body: Is it safe here to rely on `AxisDirection` to index conversion?  
created_at: 2025-02-28T22:23:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,6 +407,61 @@ BOOST_AUTO_TEST_CASE(NodeApiTestContainers) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(NodeApiTestCuboid) {
path: Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp
user: login: ssdetlab
body: Doesn't this test almost fully repeat the `MaterialCuboid` test cases from `BlueprintTests.cpp`? 
created_at: 2025-02-28T22:35:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -526,12 +529,256 @@ BOOST_AUTO_TEST_CASE(Material) {
   BOOST_CHECK_EQUAL(posDiscMat.binning().at(0).getAxis().getNBins(), 15);
   BOOST_CHECK_EQUAL(posDiscMat.binning().at(1).getAxis().getNBins(), 20);
 
-  for (std::size_t i = 2; i < child.portals().size(); i++) {
-    BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
-                      nullptr);
+  // Check outer cylinder material
+  const auto* outerCyl = child.portals()
+                             .at(static_cast<std::size_t>(OuterCylinder))
+                             .surface()
+                             .surfaceMaterial();
+  BOOST_CHECK_NE(outerCyl, nullptr);
+  const auto& outerCylMat =
+      dynamic_cast<const ProtoGridSurfaceMaterial&>(*outerCyl);
+  BOOST_CHECK_EQUAL(outerCylMat.binning().at(0).getAxis().getNBins(), 25);
+  BOOST_CHECK_EQUAL(outerCylMat.binning().at(1).getAxis().getNBins(), 30);
+
+  // Check that other faces have no material
+  for (std::size_t i = 0; i < child.portals().size(); i++) {
+    if (i != static_cast<std::size_t>(NegativeDisc) &&
+        i != static_cast<std::size_t>(PositiveDisc) &&
+        i != static_cast<std::size_t>(OuterCylinder)) {
+      BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(),
+                        nullptr);
+    }
   }
 }
 
+BOOST_AUTO_TEST_CASE(MaterialInvalidAxisDirections) {
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
+  Blueprint root{cfg};
+
+  using enum AxisDirection;
+  using enum AxisBoundaryType;
+
+  // Test invalid axis direction combinations for cylinder faces
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CylinderVolumeBounds::Face::NegativeDisc,
+                             {AxisZ, Bound, 5}, {AxisPhi, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CylinderVolumeBounds::Face::OuterCylinder,
+                             {AxisR, Bound, 5}, {AxisR, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  // Test invalid axis direction combinations for cuboid faces
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CuboidVolumeBounds::Face::NegativeXFace,
+                             {AxisX, Bound, 5}, {AxisZ, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CuboidVolumeBounds::Face::PositiveYFace,
+                             {AxisY, Bound, 5}, {AxisX, Bound, 10});
+                       }),
+      std::invalid_argument);
+
+  BOOST_CHECK_THROW(
+      root.addMaterial("Material",
+                       [&](auto& mat) {
+                         mat.configureFace(
+                             CuboidVolumeBounds::Face::NegativeZFace,
+                             {AxisZ, Bound, 5}, {AxisY, Bound, 10});
+                       }),
+      std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(MaterialMixedVolumeTypes) {
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
+  Blueprint root{cfg};
+
+  using enum AxisDirection;
+  using enum AxisBoundaryType;
+
+  // Configure for cylinder first, then try to add cuboid - should throw
+  BOOST_CHECK_THROW(
+      root.addMaterial(
+          "Material",
+          [&](auto& mat) {
+            mat.configureFace(CylinderVolumeBounds::Face::NegativeDisc,
+                              {AxisR, Bound, 5}, {AxisPhi, Bound, 10});
+            mat.configureFace(CuboidVolumeBounds::Face::NegativeXFace,
+                              {AxisX, Bound, 5}, {AxisY, Bound, 10});
+          }),
+      std::invalid_argument);
+
+  // Configure for cuboid first, then try to add cylinder - should throw
+  BOOST_CHECK_THROW(
+      root.addMaterial(
+          "Material",
+          [&](auto& mat) {
+            mat.configureFace(CuboidVolumeBounds::Face::NegativeXFace,
+                              {AxisX, Bound, 5}, {AxisY, Bound, 10});
+            mat.configureFace(CylinderVolumeBounds::Face::NegativeDisc,
+                              {AxisR, Bound, 5}, {AxisPhi, Bound, 10});
+          }),
+      std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(MaterialCuboid) {
+  Blueprint::Config cfg;
+  cfg.envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
+  cfg.envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
+  Blueprint root{cfg};
+
+  using enum AxisDirection;
+  using enum AxisBoundaryType;
+  using enum CuboidVolumeBounds::Face;
+
+  double hlX = 30_mm;
+  double hlY = 40_mm;
+  double hlZ = 50_mm;
+  auto cuboidBounds = std::make_shared<CuboidVolumeBounds>(hlX, hlY, hlZ);
+  auto cuboid = std::make_unique<TrackingVolume>(Transform3::Identity(),
+                                                 cuboidBounds, "child");
+
+  root.addMaterial("Material", [&](auto& mat) {
+    // Configure material for different faces with different binning
+    mat.configureFace(NegativeXFace, {AxisX, Bound, 5}, {AxisY, Bound, 10});
+    mat.configureFace(PositiveXFace, {AxisX, Bound, 15}, {AxisY, Bound, 20});
+    mat.configureFace(NegativeYFace, {AxisX, Bound, 25}, {AxisY, Bound, 30});
+    mat.configureFace(PositiveYFace, {AxisX, Bound, 35}, {AxisY, Bound, 40});
+    mat.configureFace(NegativeZFace, {AxisX, Bound, 45}, {AxisY, Bound, 50});
+    mat.configureFace(PositiveZFace, {AxisX, Bound, 55}, {AxisY, Bound, 60});
+
+    mat.addStaticVolume(std::move(cuboid));
+  });
+
+  auto trackingGeometry =
+      root.construct({}, gctx, *logger->clone(std::nullopt, Logging::VERBOSE));
+
+  BOOST_REQUIRE(trackingGeometry);
+
+  auto lookup = nameLookup(*trackingGeometry);
+  auto& child = lookup("child");
+
+  // Check that material is attached to all faces
+  for (std::size_t i = 0; i < child.portals().size(); i++) {
+    const auto* material = child.portals().at(i).surface().surfaceMaterial();
+    BOOST_CHECK_NE(material, nullptr);
+
+    const auto& gridMaterial =
+        dynamic_cast<const ProtoGridSurfaceMaterial&>(*material);
+
+    // Check binning based on face
+    CuboidVolumeBounds::Face face = static_cast<CuboidVolumeBounds::Face>(i);
+    switch (face) {
+      case NegativeXFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(), 5);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          10);
+        break;
+      case PositiveXFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          15);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          20);
+        break;
+      case NegativeYFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          25);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          30);
+        break;
+      case PositiveYFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          35);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          40);
+        break;
+      case NegativeZFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          45);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          50);
+        break;
+      case PositiveZFace:
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(0).getAxis().getNBins(),
+                          55);
+        BOOST_CHECK_EQUAL(gridMaterial.binning().at(1).getAxis().getNBins(),
+                          60);
+        break;
+    }
+  }
+}
+
+BOOST_AUTO_TEST_CASE(MaterialCuboidInvalidAxisCombinations) {
path: Tests/UnitTests/Core/Geometry/BlueprintTests.cpp
user: login: ssdetlab
body: Aren't these the same test cases as from `MaterialInvalidAxisDirections`? 
created_at: 2025-02-28T22:37:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,29 +116,81 @@ std::unique_ptr<TrackingGeometry> Blueprint::construct(
         {eMaxR, newBounds->get(eMaxR) + rEnv[1]},
     });
 
-    worldBounds = std::move(newBounds);
+    ACTS_DEBUG(prefix() << "Applied envelope to cylinder: Z=" << zEnv[0]
+                        << ", Rmin=" << rEnv[0] << ", Rmax=" << rEnv[1]);
+
+    world = std::make_unique<TrackingVolume>(topVolume.transform(),
+                                             std::move(newBounds), worldName);
+
+    // Need one-sided portal shell that connects outwards to nullptr
+    SingleCylinderPortalShell worldShell{*world};
+    worldShell.applyToVolume();
 
   } else if (const auto *box =
                  dynamic_cast<const CuboidVolumeBounds *>(&bounds);
              box != nullptr) {
-    throw std::logic_error{"Not implemented"};
+    ACTS_VERBOSE(prefix() << "Expanding cuboid bounds");
+    // Make a copy that we'll modify
+    auto newBounds = std::make_shared<CuboidVolumeBounds>(*box);
+
+    // Get the current half lengths
+    double halfX = newBounds->get(CuboidVolumeBounds::eHalfLengthX);
+    double halfY = newBounds->get(CuboidVolumeBounds::eHalfLengthY);
+    double halfZ = newBounds->get(CuboidVolumeBounds::eHalfLengthZ);
+
+    // Apply envelope to each dimension
+    const auto &xEnv = m_cfg.envelope[AxisX];
path: Core/src/Geometry/Blueprint.cpp
user: login: paulgessinger
body: In this case: yes, because the `operator[]` explicitly takes `AxisDirection` as an argument, and then only internally casts to the index.
created_at: 2025-03-01T10:06:39Z
author_association: MEMBER
diff_hunk: @@ -404,6 +407,61 @@ BOOST_AUTO_TEST_CASE(NodeApiTestContainers) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(NodeApiTestCuboid) {
path: Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp
user: login: paulgessinger
body: Yeah, I guess these ended up overlapping a bit more. Originally, I was trying to have the API test primarily use the `addXXX` methods on the `BlueprintNode` base class and `BlueprintTests` would only test the node properties itself. But I probably wasn't very consistent with this. I'll remove the detailed tests from this test case and leave the functionality check of the material assignmen in the other one.
created_at: 2025-03-01T10:09:55Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-02-20T15:45:01Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
Updated, the MaterialDesignatorBlueprintNode is. Support for cuboid volumes has been added alongside cylinder configurations. The BinningConfig type now holds two variants with overloaded configureFace methods for each volume type and corresponding internal validations. Legacy binning methods were removed. Python bindings and tests have been updated to reflect these changes. Enhanced error handling and stricter configuration validations now, they have.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| Core/include/Acts/.../MaterialDesignatorBlueprintNode.hpp | Updated BinningConfig variant to include cuboid; added overloaded `configureFace` methods for cylinder and cuboid; introduced validation methods; removed legacy binning accessors. |
| Core/src/Geometry/.../MaterialDesignatorBlueprintNode.cpp | Implemented new `validateCylinderFaceConfig` and `validateCuboidFaceConfig` methods; updated `configureFace` implementations and error handling; removed usage of `setBinning`. |
| Examples/Python/src/Blueprint.cpp | Removed `binning` property; added Python bindings with overloaded `configureFace` methods for cylinder and cuboid configurations. |
| Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp, BlueprintTests.cpp | Added test cases for cuboid configurations; updated tests to use `configureFace` for both volume types; introduced tests for invalid configurations and proper exception handling. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant C as Client
    participant MD as MaterialDesignatorBlueprintNode
    participant V as Validator
    C->>MD: configureFace(face, loc0, loc1)
    MD->>V: validateCylinderFaceConfig() / validateCuboidFaceConfig()
    V-->>MD: Validation result (success or error)
    MD-->>C: Return configured node or throw exception
```

## Possibly related PRs
- **acts-project/acts#4040**: The changes in the main PR, which enhance the `MaterialDesignatorBlueprintNode` class to support cuboid volume configurations, are related to the retrieved PR that introduces the `CuboidVolumeStack` class, as both involve modifications to handle cuboid volumes and their configurations.
- **acts-project/acts#3968**: The changes in the main PR are related to the modifications of the `MaterialDesignatorBlueprintNode` class, specifically the addition of methods for configuring both cylinder and cuboid faces, which aligns with the changes in the retrieved PR that also involve method signatures and configurations related to material handling.
- **acts-project/acts#4089**: The changes in the main PR are related to the modifications in the `MaterialDesignatorBlueprintNode` class, specifically the addition of methods for configuring both cylinder and cuboid faces, which aligns with the visitor pattern enhancements in the retrieved PR that also deals with geometry handling.

## Suggested labels
`Component - Documentation`, `automerge`

## Poem
> In the galaxy of code, change emerges true,  
> Cuboid and cylinder in unity, they now pursue.  
> Validate with wisdom, each face is aligned,  
> New paths in logic, meticulously designed.  
> Celebrate we must, for in code, a new force shines!  
> Hmmm… Python, tests, and methods, the future entwines.

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4629ebb9682bfcc7ea8892695c3c66745d991295 and 86abede46832105c088bf3d3fe164ff052611873.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `Core/include/Acts/Geometry/CuboidVolumeBounds.hpp` (1 hunks)
* `Examples/Python/src/Blueprint.cpp` (1 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp` (3 hunks)
* `Tests/UnitTests/Core/Geometry/BlueprintTests.cpp` (5 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Core/include/Acts/Geometry/CuboidVolumeBounds.hpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (24)</summary>

* GitHub Check: merge-sentinel
* GitHub Check: CI Bridge / lcg_107: [alma9, clang19]
* GitHub Check: CI Bridge / lcg_107: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_107: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_106a: [alma9, clang16]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc14]
* GitHub Check: CI Bridge / lcg_106a: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_105: [alma9, gcc13]
* GitHub Check: CI Bridge / lcg_105: [alma9, clang16]
* GitHub Check: CI Bridge / build_exatrkx
* GitHub Check: CI Bridge / linux_ubuntu_2204
* GitHub Check: CI Bridge / build_linux_ubuntu
* GitHub Check: CI Bridge / linux_ubuntu_2204_clang
* GitHub Check: CI Bridge / build_gnn_tensorrt
* GitHub Check: CI Bridge / build_exatrkx_cpu
* GitHub Check: CI Bridge / clang_tidy
* GitHub Check: unused_files
* GitHub Check: missing_includes
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, clang++)
* GitHub Check: macos
* GitHub Check: linux_ubuntu_extra (ubuntu2204, 20, g++)
* GitHub Check: linux_ubuntu
* GitHub Check: build_debug
* GitHub Check: docs

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp (3)</summary>

`270-278`: **Using the enum type makes the code more readable and maintainable.**

Approve I do, this change to use enum types! More clear the code becomes, less error-prone it is. Direct access to enum values through `using enum` statement, elegantly done this is.

---

`410-439`: **Well-structured test for cuboid volume configurations, hmm.**

Comprehensive test for cuboid volume configuration, this is. Proper setup of Blueprint config and testing of all six faces of the cuboid, well done it is. All valid axis combinations for each face type, tested they are. A valuable addition to ensure cuboid volumes work correctly, this test represents.

---

`424-432`: **Good testing methodology for face configurations.**

Wise it is, to test each face separately with distinct binning values. When checking results later, easier it will be, to verify correct configurations for each face. Clear intent and future debugging this approach facilitates.

</details>
<details>
<summary>Examples/Python/src/Blueprint.cpp (1)</summary>

`407-420`: **API migration to overload-based approach, well implemented it is.**

Removed the old property-based `binning` interface and replaced with method-based `configureFace` overloads, you have. Cleaner this API is, and more type-safe. Proper parameter naming with `"face"_a, "loc0"_a, "loc1"_a` notation, good practice this follows.

The overloads handle both cylinder and cuboid volume types distinctly, thus preventing mixing of incompatible configurations. Earlier error detection, this approach enables.

</details>
<details>
<summary>Tests/UnitTests/Core/Geometry/BlueprintTests.cpp (5)</summary>

`499-501`: **Updated material face configuration using new API, good this is.**

Migration from old binning approach to new `configureFace` method, correctly implemented it is. Each face configured separately with appropriate axis combinations. Clear intent this code shows.

---

`534-553`: **Thorough validation of material configuration, wise this is.**

Check both presence of materials on configured faces and absence on others, this test does. Important verification this is, ensuring material only attached where specified it should be. Complete test coverage, this approach provides.

---

`556-611`: **Comprehensive test for invalid axis configurations, essential this is.**

Test invalid configurations and verify proper exceptions thrown, you do. Both cylinder and cuboid invalid cases, covered they are. Prevent misconfigurations early, this will help. Strong validation in error handling, critical for robust code this is.

---

`613-645`: **Mixed volume type validation test, prevent subtle errors it will.**

Smart testing for error cases where mixing volume types occurs. Important to prevent runtime errors by catching configuration issues early. Type safety at configuration time rather than build time, better approach this is.

---

`647-734`: **Comprehensive cuboid material testing, impressive this is.**

Complete test for cuboid material configuration with distinct binning for each face. Check the exact binning values for each face type, you do. Exhaustive switch statement validates correct configuration for all six faces. Thorough testing approach, ensure robustness it does.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=acts-project/acts&utm_content=4105):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-02-21T09:56:09Z
body: # 📊: Physics performance monitoring for 86abede46832105c088bf3d3fe164ff052611873
[Full contents](https://acts-herald.app.cern.ch/view/acts-project/acts/2698099152)
# physmon summary
  - ✅ [Particles fatras](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/simulation/particles_fatras.html)
  - ✅ [Particles geant4](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/simulation/particles_geant4.html)
  - ✅ [Particles ttbar](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/simulation/particles_ttbar.html)
  - ✅ [Vertices ttbar](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/simulation/vertices_ttbar.html)
  - ✅ [Truth tracking (KF)](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfitting_kf/performance_trackfitting.html)
  - ✅ [Truth tracking (GSF)](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfitting_gsf/performance_trackfitting.html)
  - ✅ [Truth tracking (GX2F)](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfitting_gx2f/performance_trackfitting.html)
  - ✅ [Truth tracking (KF refit)](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackrefitting_kf/performance_trackrefitting.html)
  - ✅ [Truth tracking (GSF refit)](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackrefitting_gsf/performance_trackrefitting.html)
  - ✅ [CKF finding performance | trackfinding | single muon | truth smeared seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_smeared/performance_finding_ckf.html)
  - ✅ [CKF fitting performance | trackfinding | single muon | truth smeared seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_smeared/performance_fitting_ckf.html)
  - ✅ [CKF track summary | trackfinding | single muon | truth smeared seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_smeared/tracksummary_ckf.html)
  - ✅ [Seeding trackfinding | single muon | truth estimated seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_estimated/performance_seeding.html)
  - ✅ [CKF finding performance | trackfinding | single muon | truth estimated seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_estimated/performance_finding_ckf.html)
  - ✅ [CKF fitting performance | trackfinding | single muon | truth estimated seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_estimated/performance_fitting_ckf.html)
  - ✅ [CKF track summary | trackfinding | single muon | truth estimated seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/truth_estimated/tracksummary_ckf.html)
  - ✅ [Seeding trackfinding | single muon | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/seeded/performance_seeding.html)
  - ✅ [CKF finding performance | trackfinding | single muon | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/seeded/performance_finding_ckf.html)
  - ✅ [CKF fitting performance | trackfinding | single muon | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/seeded/performance_fitting_ckf.html)
  - ✅ [CKF track summary | trackfinding | single muon | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/seeded/tracksummary_ckf.html)
  - ✅ [Seeding trackfinding | single muon | orthogonal seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/orthogonal/performance_seeding.html)
  - ✅ [CKF finding performance | trackfinding | single muon | orthogonal seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/orthogonal/performance_finding_ckf.html)
  - ✅ [CKF fitting performance | trackfinding | single muon | orthogonal seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/orthogonal/performance_fitting_ckf.html)
  - ✅ [CKF track summary | trackfinding | single muon | orthogonal seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_1muon/orthogonal/tracksummary_ckf.html)
  - ✅ [Seeding trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_seeding.html)
  - ✅ [CKF finding performance | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_finding_ckf.html)
  - ✅ [CKF fitting performance | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_fitting_ckf.html)
  - ✅ [CKF track summary | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/tracksummary_ckf.html)
  - ✅ [Ambisolver finding performance | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_finding_ckf_ambi.html)
  - ✅ [IVF notime | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_vertexing_ivf_notime.html)
  - ✅ [AMVF gauss notime | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_vertexing_amvf_gauss_notime.html)
  - ✅ [AMVF grid time | trackfinding | 4 muon x 50 vertices | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_4muon_50vertices/performance_vertexing_amvf_grid_time.html)
  - ✅ [Seeding trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_seeding.html)
  - ✅ [CKF finding performance | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_finding_ckf.html)
  - ✅ [CKF fitting performance | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_fitting_ckf.html)
  - ✅ [CKF track summary | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/tracksummary_ckf.html)
  - ✅ [Ambisolver finding performance | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_finding_ckf_ambi.html)
  - ✅ [ML Ambisolver | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_finding_ckf_ml_solver.html)
  - ✅ [AMVF gauss notime | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_vertexing_amvf_gauss_notime.html)
  - ✅ [AMVF grid time | trackfinding | ttbar with 200 pileup | default seeding](https://acts-herald.app.cern.ch/view/acts-project/acts/runs/13680890250/artifacts/physmon/html/trackfinding_ttbar_pu200/performance_vertexing_amvf_grid_time.html)

author_association: CONTRIBUTOR
user: login: paulgessinger
created_at: 2025-02-24T08:52:24Z
body: @ssdetlab let's absolutely merge https://github.com/acts-project/acts/pull/4047 before this one.
author_association: MEMBER
user: login: paulgessinger
created_at: 2025-02-28T13:40:48Z
body: @ssdetlab #4047 is in, should we merge this one next?
author_association: MEMBER
user: login: paulgessinger
created_at: 2025-02-28T15:53:11Z
body: @ssdetlab added proper cuboid support now, and expanded tests.
author_association: MEMBER
user: login: paulgessinger
created_at: 2025-03-01T10:16:14Z
body: @ssdetlab I removed the overlapping test cases that you commented on.
author_association: MEMBER
user: login: paulgessinger
created_at: 2025-03-03T15:52:23Z
body: @ssdetlab updated now and fixed the unit test.
author_association: MEMBER
user: login: kodiakhq[bot]
created_at: 2025-03-05T10:33:32Z
body: 
This PR currently has a merge conflict. Please resolve this and then re-add the `automerge` label.

author_association: CONTRIBUTOR
user: login: paulgessinger
created_at: 2025-03-05T16:37:33Z
body: @ssdetlab Fixing the conflicst invalidated your review. Can you click the button again?
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-03-05T18:00:43Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=acts-project_acts&pullRequest=4105) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=acts-project_acts&pullRequest=4105&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=acts-project_acts&pullRequest=4105&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=acts-project_acts&pullRequest=4105&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [43.6% Coverage on New Code](https://sonarcloud.io/component_measures?id=acts-project_acts&pullRequest=4105&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=acts-project_acts&pullRequest=4105&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=acts-project_acts&pullRequest=4105)


author_association: NONE
user: login: acts-project-service
created_at: 2025-03-06T08:03:19Z
body: ## :white_check_mark: Athena integration test results
### :white_check_mark: All tests successful

|status|job|report|
|------|---|------|
|:green_circle: | [report_pull_request](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351415)||
|:green_circle: | [run_workflow_tests_run4_mc](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351414)||
|:green_circle: | [run_workflow_tests_run2_mc](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351413)||
|:green_circle: | [run_workflow_tests_run2_data](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351412)||
|:green_circle: | [run_workflow_tests_run3_mc](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351411)||
|:green_circle: | [run_workflow_tests_run3_data](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351410)||
|:green_circle: | [test_ActsDumpGeometryIdentifiers](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351409)||
|:green_circle: | [test_ActsCheckObjectCountsWorkflowHgtd](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351408)||
|:green_circle: | [test_ActsCheckObjectCountsCachedWorkflow](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351407)||
|:green_circle: | [test_ActsCheckObjectCountsWorkflow](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351406)||
|:green_circle: | [test_ActsEFTrackFit](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351405)||
|:green_circle: | [test_ActsPersistifySeeds](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351404)||
|:green_circle: | [test_ActsBenchmarkWithSpot](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351403)||
|:green_circle: | [test_ActsAnalogueClustering](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351402)||
|:green_circle: | [test_ActsWorkflowHeavyIons](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351401)||
|:green_circle: | [test_ActsWorkflowFastTracking](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351400)||
|:green_circle: | [test_ActsWorkflowCached](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351399)||
|:green_circle: | [test_ActsWorkflow](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351398)||
|:green_circle: | [test_ActsValidateAmbiguityResolution](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351397)||
|:green_circle: | [test_ActsValidateResolvedTracks](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351396)||
|:green_circle: | [test_ActsValidateTracks](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351395)||
|:green_circle: | [test_ActsValidateActsCoreSpacePoints](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351394)||
|:green_circle: | [test_ActsValidateActsSpacePoints](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351393)||
|:green_circle: | [test_ActsValidateSeeds](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351392)||
|:green_circle: | [test_ActsValidateOrthogonalSeeds](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351391)||
|:green_circle: | [test_ActsValidateClusters](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351390)||
|:green_circle: | [test_ActsPersistifyEDM](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351389)||
|:green_circle: | [test_ActsGx2fRefitting](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351388)||
|:green_circle: | [test_ActsGSFRefitting](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351387)||
|:green_circle: | [test_ActsKfRefitting](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351386)||
|:green_circle: | [test_ActsExtrapolationAlgTest](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351385)||
|:green_circle: | [test_ActsITkTest](https://gitlab.cern.ch/acts/acts-athena-ci/-/jobs/52351384)||

author_association: COLLABORATOR