File indexing completed on 2026-08-09 08:18:17
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/ContainerBlueprintNode.hpp"
0010
0011 #include "Acts/Geometry/CuboidPortalShell.hpp"
0012 #include "Acts/Geometry/CuboidVolumeStack.hpp"
0013 #include "Acts/Geometry/CylinderPortalShell.hpp"
0014 #include "Acts/Geometry/CylinderVolumeStack.hpp"
0015 #include "Acts/Geometry/Portal.hpp"
0016 #include "Acts/Surfaces/RegularSurface.hpp"
0017
0018 #include <sstream>
0019 #include <string>
0020 #include <vector>
0021
0022 namespace Acts {
0023
0024 ContainerBlueprintNode::ContainerBlueprintNode(
0025 const std::string& name, AxisDirection axis,
0026 VolumeAttachmentStrategy attachmentStrategy,
0027 VolumeResizeStrategy resizeStrategy)
0028 : ContainerBlueprintNode(name, axis, attachmentStrategy,
0029 {resizeStrategy, resizeStrategy}) {}
0030
0031 ContainerBlueprintNode::ContainerBlueprintNode(
0032 const std::string& name, AxisDirection axis,
0033 VolumeAttachmentStrategy attachmentStrategy,
0034 std::pair<VolumeResizeStrategy, VolumeResizeStrategy> resizeStrategies)
0035 : m_name(name),
0036 m_direction(axis),
0037 m_attachmentStrategy(attachmentStrategy),
0038 m_resizeStrategies(resizeStrategies) {}
0039
0040 const std::string& ContainerBlueprintNode::name() const {
0041 return m_name;
0042 }
0043
0044 Volume& ContainerBlueprintNode::build(const BlueprintOptions& options,
0045 const GeometryContext& gctx,
0046 const Logger& logger) {
0047 ACTS_DEBUG(prefix() << "container build (dir=" << m_direction << ")");
0048
0049 if (m_stack != nullptr) {
0050 ACTS_ERROR(prefix() << "Volume is already built");
0051 throw std::runtime_error("Volume is already built");
0052 }
0053
0054 for (auto& child : children()) {
0055 Volume& volume = child.build(options, gctx, logger);
0056 m_childVolumes.push_back(&volume);
0057
0058
0059 m_volumeToNode[&volume] = &child;
0060 }
0061 ACTS_VERBOSE(prefix() << "-> Collected " << m_childVolumes.size()
0062 << " child volumes");
0063 ACTS_VERBOSE(prefix() << "-> Building the stack");
0064 m_stack = makeStack(gctx, m_childVolumes, logger);
0065 ACTS_DEBUG(prefix() << "-> Stack bounds are: " << m_stack->volumeBounds());
0066
0067 ACTS_DEBUG(prefix() << " *** build complete ***");
0068
0069 return *m_stack;
0070 }
0071
0072 void ContainerBlueprintNode::finalize(const BlueprintOptions& options,
0073 const GeometryContext& gctx,
0074 TrackingVolume& parent,
0075 const Logger& logger) {
0076 ACTS_DEBUG(prefix() << "Finalizing container");
0077
0078 if (m_stack == nullptr) {
0079 ACTS_ERROR(prefix() << "Volume is not built");
0080 throw std::runtime_error("Volume is not built");
0081 }
0082
0083 if (m_shell == nullptr) {
0084 ACTS_ERROR(prefix() << "Volume is not connected");
0085 throw std::runtime_error("Volume is not connected");
0086 }
0087
0088 const auto* policyFactory = options.defaultNavigationPolicyFactory.get();
0089
0090 ACTS_DEBUG(prefix() << "Registering " << m_gaps.size()
0091 << " gap volumes with parent");
0092 for (auto& [shell, gap] : m_gaps) {
0093 auto* gapPtr = gap.get();
0094 parent.addVolume(std::move(gap));
0095 shell->applyToVolume();
0096 auto policy = policyFactory->build(gctx, *gapPtr, logger);
0097 gapPtr->setNavigationPolicy(std::move(policy));
0098 }
0099
0100 ACTS_DEBUG(prefix() << "Finalizing " << children().size() << " children");
0101
0102 for (auto& child : children()) {
0103 child.finalize(options, gctx, parent, logger);
0104 }
0105 }
0106
0107 ContainerBlueprintNode& ContainerBlueprintNode::setDirection(
0108 AxisDirection direction) {
0109 if (m_stack != nullptr) {
0110 throw std::runtime_error("Cannot change direction after build");
0111 }
0112 m_direction = direction;
0113 return *this;
0114 }
0115
0116 ContainerBlueprintNode& ContainerBlueprintNode::setAttachmentStrategy(
0117 VolumeAttachmentStrategy attachmentStrategy) {
0118 if (m_stack != nullptr) {
0119 throw std::runtime_error("Cannot change direction after build");
0120 }
0121 m_attachmentStrategy = attachmentStrategy;
0122 return *this;
0123 }
0124
0125 ContainerBlueprintNode& ContainerBlueprintNode::setResizeStrategy(
0126 VolumeResizeStrategy resizeStrategy) {
0127 if (m_stack != nullptr) {
0128 throw std::runtime_error("Cannot change direction after build");
0129 }
0130 m_resizeStrategies = {resizeStrategy, resizeStrategy};
0131 return *this;
0132 }
0133
0134 ContainerBlueprintNode& ContainerBlueprintNode::setResizeStrategies(
0135 VolumeResizeStrategy inner, VolumeResizeStrategy outer) {
0136 if (m_stack != nullptr) {
0137 throw std::runtime_error("Cannot change direction after build");
0138 }
0139 m_resizeStrategies = {inner, outer};
0140 return *this;
0141 }
0142
0143 AxisDirection ContainerBlueprintNode::direction() const {
0144 return m_direction;
0145 }
0146
0147 VolumeAttachmentStrategy ContainerBlueprintNode::attachmentStrategy() const {
0148 return m_attachmentStrategy;
0149 }
0150
0151 std::pair<VolumeResizeStrategy, VolumeResizeStrategy>
0152 ContainerBlueprintNode::resizeStrategies() const {
0153 return m_resizeStrategies;
0154 }
0155
0156 void ContainerBlueprintNode::addToGraphviz(std::ostream& os) const {
0157 std::stringstream ss;
0158 ss << "<b>" + name() + "</b>";
0159 ss << "<br/>" << typeName() << "Container";
0160 ss << "<br/>dir: " << m_direction;
0161 GraphViz::Node node{
0162 .id = name(), .label = ss.str(), .shape = GraphViz::Shape::DoubleOctagon};
0163 os << node << std::endl;
0164 for (const auto& child : children()) {
0165 os << indent() << GraphViz::Edge{{.id = name()}, {.id = child.name()}}
0166 << std::endl;
0167 child.addToGraphviz(os);
0168 }
0169 }
0170
0171 template <typename BaseShell, typename SingleShell>
0172 std::vector<BaseShell*> ContainerBlueprintNode::collectChildShells(
0173 const BlueprintOptions& options, const GeometryContext& gctx,
0174 VolumeStack& stack, const std::string& prefix, const Logger& logger) {
0175 std::vector<BaseShell*> shells;
0176 ACTS_DEBUG(prefix << "Have " << m_childVolumes.size() << " child volumes");
0177 std::size_t nGaps = 0;
0178 for (Volume* volume : m_childVolumes) {
0179 if (stack.isGapVolume(*volume)) {
0180
0181
0182 auto gap = std::make_unique<TrackingVolume>(*volume);
0183 gap->setVolumeName(name() + "::Gap" + std::to_string(nGaps + 1));
0184 nGaps++;
0185 ACTS_DEBUG(prefix << " ~> Gap volume (" << gap->volumeName()
0186 << "): " << gap->volumeBounds());
0187 auto shell = std::make_unique<SingleShell>(gctx, *gap);
0188 assert(shell->isValid());
0189 shells.push_back(shell.get());
0190
0191 m_gaps.emplace_back(std::move(shell), std::move(gap));
0192
0193 } else {
0194
0195 auto it = m_volumeToNode.find(volume);
0196 if (it == m_volumeToNode.end()) {
0197 throw std::runtime_error("Volume not found in child volumes");
0198 }
0199
0200 BlueprintNode& child = *it->second;
0201
0202 ACTS_DEBUG(prefix << " ~> Child (" << child.name()
0203 << ") volume: " << volume->volumeBounds());
0204
0205 auto* shell =
0206 dynamic_cast<BaseShell*>(&child.connect(options, gctx, logger));
0207 if (shell == nullptr) {
0208 ACTS_ERROR(prefix << "Child volume stack type mismatch");
0209 throw std::runtime_error("Child volume stack type mismatch");
0210 }
0211 assert(shell->isValid());
0212
0213 shells.push_back(shell);
0214 }
0215 }
0216 return shells;
0217 }
0218
0219 template <typename BaseShell, typename SingleShell, typename ShellStack>
0220 PortalShellBase& ContainerBlueprintNode::connectImpl(
0221 const BlueprintOptions& options, const GeometryContext& gctx,
0222 VolumeStack* stack, const std::string& prefix, const Logger& logger) {
0223 ACTS_DEBUG(prefix << "Container connect");
0224 if (stack == nullptr) {
0225 ACTS_ERROR(prefix << "Volume is not built");
0226 throw std::runtime_error("Volume is not built");
0227 }
0228 ACTS_DEBUG(prefix << "Collecting child shells from " << children().size()
0229 << " children");
0230
0231
0232
0233
0234
0235 std::vector<BaseShell*> shells = collectChildShells<BaseShell, SingleShell>(
0236 options, gctx, *stack, prefix, logger);
0237
0238
0239 throw_assert(shells.size() == m_childVolumes.size(),
0240 "Number of shells does not match number of child volumes");
0241
0242 throw_assert(std::ranges::none_of(
0243 shells, [](const auto* shell) { return shell == nullptr; }),
0244 "Invalid shell pointer");
0245
0246 throw_assert(std::ranges::all_of(
0247 shells, [](const auto* shell) { return shell->isValid(); }),
0248 "Invalid shell");
0249
0250
0251
0252
0253
0254
0255
0256
0257 const PortalMaterialMergePolicy materialPolicy =
0258 options.keepGoingOnMaterialMergeFailure
0259 ? PortalMaterialMergePolicy::eDiscardAndMark
0260 : PortalMaterialMergePolicy::eThrow;
0261
0262
0263
0264 std::vector<std::string> materialClashes;
0265 for (auto face : shells.size() > 1
0266 ? ShellStack::mergedFaces(direction())
0267 : std::vector<typename ShellStack::Face>{}) {
0268 for (auto* shell : shells) {
0269 auto portal = shell->portal(face);
0270 if (portal != nullptr && portal->surface().hasMaterial()) {
0271 std::stringstream ss;
0272 ss << shell->label() << " carries material on face " << face;
0273 materialClashes.push_back(ss.str());
0274 }
0275 }
0276 }
0277 if (!materialClashes.empty()) {
0278 std::stringstream ss;
0279 ss << prefix << "Material is designated on portal faces that are merged "
0280 << "when stacking child volumes in " << direction() << " direction.";
0281 for (const auto& clash : materialClashes) {
0282 ss << "\n - " << clash;
0283 }
0284 if (materialPolicy == PortalMaterialMergePolicy::eThrow) {
0285 ss << "\nMove the material designation to a face that is not merged "
0286 "(e.g. "
0287 "the enclosing container's face).";
0288 ACTS_ERROR(ss.str());
0289 throw PortalMergingException{ss.str()};
0290 }
0291 ss << "\nContinuing anyway: this material will be discarded and the merged "
0292 "surface tagged with a MergedMaterialMarker.";
0293 ACTS_WARNING(ss.str());
0294 }
0295
0296 ACTS_DEBUG(prefix << "Producing merged stack shell in " << direction()
0297 << " direction from " << shells.size() << " shells");
0298 m_shell = std::make_unique<ShellStack>(gctx, std::move(shells), direction(),
0299 logger, materialPolicy);
0300
0301 assert(m_shell != nullptr && "No shell was built at the end of connect");
0302 assert(m_shell->isValid() && "Shell is not valid at the end of connect");
0303 return *m_shell;
0304 }
0305
0306 PortalShellBase& CylinderContainerBlueprintNode::connect(
0307 const BlueprintOptions& options, const GeometryContext& gctx,
0308 const Logger& logger) {
0309 return connectImpl<CylinderPortalShell, SingleCylinderPortalShell,
0310 CylinderStackPortalShell>(options, gctx, m_stack.get(),
0311 prefix(), logger);
0312 }
0313
0314 const std::string& CylinderContainerBlueprintNode::typeName() const {
0315 return s_typeName;
0316 }
0317
0318 std::unique_ptr<VolumeStack> CylinderContainerBlueprintNode::makeStack(
0319 const GeometryContext& gctx, std::vector<Volume*>& volumes,
0320 const Logger& logger) {
0321 return std::make_unique<CylinderVolumeStack>(gctx, volumes, m_direction,
0322 m_attachmentStrategy,
0323 m_resizeStrategies, logger);
0324 }
0325
0326 PortalShellBase& CuboidContainerBlueprintNode::connect(
0327 const BlueprintOptions& options, const GeometryContext& gctx,
0328 const Logger& logger) {
0329 return connectImpl<CuboidPortalShell, SingleCuboidPortalShell,
0330 CuboidStackPortalShell>(options, gctx, m_stack.get(),
0331 prefix(), logger);
0332 }
0333
0334 const std::string& CuboidContainerBlueprintNode::typeName() const {
0335 return s_typeName;
0336 }
0337
0338 std::unique_ptr<VolumeStack> CuboidContainerBlueprintNode::makeStack(
0339 const GeometryContext& gctx, std::vector<Volume*>& volumes,
0340 const Logger& logger) {
0341 return std::make_unique<CuboidVolumeStack>(gctx, volumes, m_direction,
0342 m_attachmentStrategy,
0343 m_resizeStrategies.first, logger);
0344 }
0345
0346 }