File indexing completed on 2025-07-12 07:51:53
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Geometry/Blueprint.hpp"
0010
0011 #include "Acts/Geometry/CuboidPortalShell.hpp"
0012 #include "Acts/Geometry/CuboidVolumeBounds.hpp"
0013 #include "Acts/Geometry/CylinderPortalShell.hpp"
0014 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0015 #include "Acts/Geometry/Extent.hpp"
0016 #include "Acts/Geometry/GeometryIdentifier.hpp"
0017 #include "Acts/Geometry/PortalShell.hpp"
0018 #include "Acts/Geometry/VolumeBounds.hpp"
0019 #include "Acts/Navigation/INavigationPolicy.hpp"
0020 #include "Acts/Navigation/TryAllNavigationPolicy.hpp"
0021 #include "Acts/Utilities/GraphViz.hpp"
0022 #include "Acts/Utilities/Logger.hpp"
0023
0024 #include <algorithm>
0025
0026 namespace {
0027 const std::string s_rootName = "Root";
0028 }
0029
0030 namespace Acts::Experimental {
0031
0032
0033
0034 class BlueprintVisitor : public TrackingGeometryMutableVisitor {
0035 public:
0036 explicit BlueprintVisitor(
0037 const Logger &logger,
0038 std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0039 &volumesById)
0040 : TrackingGeometryMutableVisitor(true),
0041 m_volumesById(volumesById),
0042 m_logger(logger) {}
0043
0044 void visitVolume(TrackingVolume &volume) override {
0045 GeometryIdentifier::Value iportal = 0;
0046 GeometryIdentifier::Value isensitive = 0;
0047
0048 auto id = volume.geometryId();
0049
0050 if (id == GeometryIdentifier{}) {
0051 auto it = std::ranges::find(m_volumesById, nullptr);
0052 if (it == m_volumesById.end()) {
0053 ACTS_ERROR("No free volume IDs left, all " << m_volumesById.size()
0054 << " are used");
0055
0056 throw std::logic_error("No free volume IDs left");
0057 }
0058
0059 id = GeometryIdentifier().withVolume(
0060 std::distance(m_volumesById.begin(), it) + 1);
0061
0062 ACTS_DEBUG("Assigning volume ID " << id << " for "
0063 << volume.volumeName());
0064 volume.assignGeometryId(id);
0065 *it = &volume;
0066 }
0067
0068 for (auto &portal : volume.portals()) {
0069 if (portal.surface().geometryId() != GeometryIdentifier{}) {
0070 continue;
0071 }
0072 iportal += 1;
0073 auto portalId = id.withBoundary(iportal);
0074 ACTS_DEBUG("Assigning portal ID: " << portalId);
0075 portal.surface().assignGeometryId(portalId);
0076 }
0077 for (auto &surface : volume.surfaces()) {
0078 if (surface.geometryId() != GeometryIdentifier{}) {
0079 continue;
0080 }
0081 isensitive += 1;
0082 auto surfaceId = id.withSensitive(isensitive);
0083 ACTS_DEBUG("Assigning surface ID: " << surfaceId);
0084 surface.assignGeometryId(surfaceId);
0085 }
0086 }
0087
0088 private:
0089 std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0090 &m_volumesById;
0091 const Logger &m_logger;
0092 const Acts::Logger &logger() const { return m_logger; }
0093 };
0094
0095 Blueprint::Blueprint(const Config &config) : m_cfg(config) {}
0096
0097 const std::string &Blueprint::name() const {
0098 return s_rootName;
0099 }
0100
0101 Volume &Blueprint::build(const BlueprintOptions & ,
0102 const GeometryContext & ,
0103 const Logger & ) {
0104 throw std::logic_error("Root node cannot be built");
0105 }
0106
0107 PortalShellBase &Blueprint::connect(const BlueprintOptions & ,
0108 const GeometryContext & ,
0109 const Logger & ) {
0110 throw std::logic_error("Root node cannot be connected");
0111 }
0112
0113 void Blueprint::finalize(const BlueprintOptions & ,
0114 const GeometryContext & ,
0115 TrackingVolume & ,
0116 const Logger & ) {
0117 throw std::logic_error("Root node cannot be finalized");
0118 }
0119
0120 void Blueprint::addToGraphviz(std::ostream &os) const {
0121 GraphViz::Node node{
0122 .id = name(), .label = "World", .shape = GraphViz::Shape::House};
0123
0124 os << node;
0125 BlueprintNode::addToGraphviz(os);
0126 }
0127
0128 std::unique_ptr<TrackingGeometry> Blueprint::construct(
0129 const BlueprintOptions &options, const GeometryContext &gctx,
0130 const Logger &logger) {
0131 using enum AxisDirection;
0132
0133 ACTS_INFO(prefix() << "Building tracking geometry from blueprint tree");
0134
0135 options.validate();
0136
0137 if (m_cfg.envelope == ExtentEnvelope::Zero()) {
0138 ACTS_WARNING(prefix() << "Root node is configured with zero envelope. This "
0139 "might lead to navigation issues");
0140 }
0141
0142 if (children().size() != 1) {
0143 ACTS_ERROR(prefix() << "Root node must have exactly one child");
0144 throw std::logic_error("Root node must have exactly one child");
0145 }
0146
0147 auto &child = children().at(0);
0148
0149 ACTS_DEBUG(prefix() << "Executing building on tree");
0150 Volume &topVolume = child.build(options, gctx, logger);
0151 const auto &bounds = topVolume.volumeBounds();
0152
0153 std::stringstream ss;
0154 bounds.toStream(ss);
0155 ACTS_DEBUG(prefix() << "have top volume: " << ss.str() << "\n"
0156 << topVolume.transform().matrix());
0157
0158 std::unique_ptr<TrackingVolume> world;
0159 static const std::string worldName = "World";
0160
0161 if (const auto *cyl = dynamic_cast<const CylinderVolumeBounds *>(&bounds);
0162 cyl != nullptr) {
0163 ACTS_VERBOSE(prefix() << "Expanding cylinder bounds");
0164 using enum CylinderVolumeBounds::BoundValues;
0165
0166
0167 auto newBounds = std::make_shared<CylinderVolumeBounds>(*cyl);
0168
0169 const auto &zEnv = m_cfg.envelope[AxisZ];
0170 if (zEnv[0] != zEnv[1]) {
0171 ACTS_ERROR(
0172 prefix() << "Root node cylinder envelope for z must be symmetric");
0173 throw std::logic_error(
0174 "Root node cylinder envelope for z must be "
0175 "symmetric");
0176 }
0177
0178 const auto &rEnv = m_cfg.envelope[AxisR];
0179
0180 newBounds->set({
0181 {eHalfLengthZ, newBounds->get(eHalfLengthZ) + zEnv[0]},
0182 {eMinR, std::max(0.0, newBounds->get(eMinR) - rEnv[0])},
0183 {eMaxR, newBounds->get(eMaxR) + rEnv[1]},
0184 });
0185
0186 ACTS_DEBUG(prefix() << "Applied envelope to cylinder: Z=" << zEnv[0]
0187 << ", Rmin=" << rEnv[0] << ", Rmax=" << rEnv[1]);
0188
0189 world = std::make_unique<TrackingVolume>(topVolume.transform(),
0190 std::move(newBounds), worldName);
0191
0192
0193 SingleCylinderPortalShell worldShell{*world};
0194 worldShell.applyToVolume();
0195
0196 } else if (const auto *box =
0197 dynamic_cast<const CuboidVolumeBounds *>(&bounds);
0198 box != nullptr) {
0199 ACTS_VERBOSE(prefix() << "Expanding cuboid bounds");
0200
0201 auto newBounds = std::make_shared<CuboidVolumeBounds>(*box);
0202
0203
0204 double halfX = newBounds->get(CuboidVolumeBounds::eHalfLengthX);
0205 double halfY = newBounds->get(CuboidVolumeBounds::eHalfLengthY);
0206 double halfZ = newBounds->get(CuboidVolumeBounds::eHalfLengthZ);
0207
0208
0209 const auto &xEnv = m_cfg.envelope[AxisX];
0210 const auto &yEnv = m_cfg.envelope[AxisY];
0211 const auto &zEnv = m_cfg.envelope[AxisZ];
0212
0213
0214 if (xEnv[0] != xEnv[1]) {
0215 ACTS_ERROR(
0216 prefix() << "Root node cuboid envelope for X must be symmetric");
0217 throw std::logic_error(
0218 "Root node cuboid envelope for X must be symmetric");
0219 }
0220
0221 if (yEnv[0] != yEnv[1]) {
0222 ACTS_ERROR(
0223 prefix() << "Root node cuboid envelope for Y must be symmetric");
0224 throw std::logic_error(
0225 "Root node cuboid envelope for Y must be symmetric");
0226 }
0227
0228 if (zEnv[0] != zEnv[1]) {
0229 ACTS_ERROR(
0230 prefix() << "Root node cuboid envelope for Z must be symmetric");
0231 throw std::logic_error(
0232 "Root node cuboid envelope for Z must be symmetric");
0233 }
0234
0235 newBounds->set({
0236 {CuboidVolumeBounds::eHalfLengthX, halfX + xEnv[0]},
0237 {CuboidVolumeBounds::eHalfLengthY, halfY + yEnv[0]},
0238 {CuboidVolumeBounds::eHalfLengthZ, halfZ + zEnv[0]},
0239 });
0240
0241 ACTS_DEBUG(prefix() << "Applied envelope to cuboid: X=" << xEnv[0]
0242 << ", Y=" << yEnv[0] << ", Z=" << zEnv[0]);
0243
0244 world = std::make_unique<TrackingVolume>(topVolume.transform(),
0245 std::move(newBounds), worldName);
0246
0247
0248 SingleCuboidPortalShell worldShell{*world};
0249 worldShell.applyToVolume();
0250
0251 } else {
0252 throw std::logic_error{"Unsupported volume bounds type"};
0253 }
0254
0255 ACTS_DEBUG(prefix() << "New root volume bounds are: "
0256 << world->volumeBounds());
0257
0258 world->setNavigationPolicy(std::make_unique<Acts::TryAllNavigationPolicy>(
0259 gctx, *world, logger, Acts::TryAllNavigationPolicy::Config{}));
0260
0261 auto &shell = child.connect(options, gctx, logger);
0262
0263 shell.fill(*world);
0264
0265 child.finalize(options, gctx, *world, logger);
0266
0267 std::set<std::string, std::less<>> volumeNames;
0268 std::array<const TrackingVolume *, GeometryIdentifier::getMaxVolume()>
0269 volumesById{};
0270 volumesById.fill(nullptr);
0271
0272
0273
0274 world->apply([&, this](TrackingVolume &volume) {
0275 if (volumeNames.contains(volume.volumeName())) {
0276 ACTS_ERROR(prefix() << "Duplicate volume name: " << volume.volumeName());
0277 throw std::logic_error("Duplicate volume name");
0278 }
0279 volumeNames.insert(volume.volumeName());
0280
0281 if (volume.geometryId() != GeometryIdentifier{}) {
0282
0283
0284 if (volumesById.at(volume.geometryId().volume() - 1) == nullptr) {
0285 volumesById.at(volume.geometryId().volume() - 1) = &volume;
0286 }
0287 }
0288
0289
0290 volume.clearBoundarySurfaces();
0291 });
0292
0293 std::size_t unusedVolumeIds = std::ranges::count(volumesById, nullptr);
0294 ACTS_DEBUG(prefix() << "Number of unused volume IDs: " << unusedVolumeIds);
0295
0296 ACTS_DEBUG(prefix() << "Assigning volume IDs for remaining volumes");
0297
0298 BlueprintVisitor visitor{logger, volumesById};
0299 world->apply(visitor);
0300
0301 return std::make_unique<TrackingGeometry>(
0302 std::move(world), nullptr, GeometryIdentifierHook{}, logger, false);
0303 }
0304
0305 }