File indexing completed on 2025-09-17 08:03:26
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Plugins/ActSVG/TrackingGeometrySvgConverter.hpp"
0010
0011 #include "Acts/Geometry/CompositePortalLink.hpp"
0012 #include "Acts/Geometry/CylinderVolumeBounds.hpp"
0013 #include "Acts/Geometry/GeometryContext.hpp"
0014 #include "Acts/Geometry/GridPortalLink.hpp"
0015 #include "Acts/Geometry/TrackingGeometry.hpp"
0016 #include "Acts/Geometry/TrackingVolume.hpp"
0017 #include "Acts/Navigation/SurfaceArrayNavigationPolicy.hpp"
0018 #include "Acts/Plugins/ActSVG/DetectorVolumeSvgConverter.hpp"
0019 #include "Acts/Plugins/ActSVG/PortalSvgConverter.hpp"
0020 #include "Acts/Plugins/ActSVG/SurfaceArraySvgConverter.hpp"
0021 #include "Acts/Plugins/ActSVG/SurfaceSvgConverter.hpp"
0022 #include "Acts/Utilities/Zip.hpp"
0023
0024 #include <sstream>
0025 #include <stdexcept>
0026
0027 namespace Acts::Svg {
0028
0029 std::vector<actsvg::svg::object> TrackingGeometryConverter::convert(
0030 const GeometryContext& gctx, const TrackingGeometry& tGeometry,
0031 const TrackingGeometryConverter::Options& cOptions) {
0032
0033 const TrackingVolume* world = tGeometry.highestTrackingVolume();
0034
0035
0036 TrackingGeometryConverter::State cState;
0037
0038
0039 convert(gctx, *world, cOptions, cState);
0040
0041
0042 std::vector<actsvg::svg::object> finalViews = cState.finalViews;
0043 if (!cState.xyCrossSection.empty()) {
0044 finalViews.push_back(
0045 Acts::Svg::group(cState.xyCrossSection, cOptions.prefix + "layers_xy"));
0046 }
0047 if (!cState.zrCrossSection.empty()) {
0048 finalViews.push_back(
0049 Acts::Svg::group(cState.zrCrossSection, cOptions.prefix + "layers_zr"));
0050 }
0051
0052 return finalViews;
0053 }
0054
0055 void TrackingGeometryConverter::convert(
0056 const GeometryContext& gctx, const TrackingVolume& tVolume,
0057 const TrackingGeometryConverter::Options& cOptions,
0058 TrackingGeometryConverter::State& cState) {
0059
0060 if (tVolume.confinedLayers() != nullptr) {
0061 for (const auto& layer : tVolume.confinedLayers()->arrayObjects()) {
0062 if (layer->surfaceArray() != nullptr) {
0063 GeometryIdentifier geoID = layer->geometryId();
0064 std::string layerName = cOptions.prefix + "vol_" +
0065 std::to_string(geoID.volume()) + "_layer_" +
0066 std::to_string(geoID.layer());
0067
0068 LayerConverter::Options lOptions;
0069
0070 auto deflOptions = cOptions.layerOptions.find(geoID);
0071 if (deflOptions != cOptions.layerOptions.end()) {
0072 lOptions = (*deflOptions);
0073 lOptions.name = layerName;
0074 }
0075
0076 auto layerSheets = LayerConverter::convert(gctx, *layer, lOptions);
0077
0078
0079 for (const auto& lSheet : layerSheets) {
0080 if (lSheet.is_defined()) {
0081 cState.finalViews.push_back(lSheet);
0082 }
0083 }
0084
0085 if (layerSheets[LayerConverter::eCrossSectionXY].is_defined() &&
0086 layer->surfaceRepresentation().type() == Acts::Surface::Cylinder) {
0087 cState.xyCrossSection.push_back(
0088 layerSheets[LayerConverter::eCrossSectionXY]);
0089 }
0090
0091 if (layerSheets[LayerConverter::eCrossSectionZR].is_defined()) {
0092 cState.zrCrossSection.push_back(
0093 layerSheets[LayerConverter::eCrossSectionZR]);
0094 }
0095 }
0096 }
0097 }
0098
0099
0100 if (tVolume.confinedVolumes() != nullptr) {
0101 for (const auto& volume : tVolume.confinedVolumes()->arrayObjects()) {
0102 convert(gctx, *volume, cOptions, cState);
0103 }
0104 }
0105 }
0106
0107 std::array<actsvg::svg::object, 2> TrackingGeometryProjections::convert(
0108 const GeometryContext& gctx, const Acts::TrackingGeometry& tGeometry,
0109 const TrackingGeometryProjections::Options& cOptions) {
0110
0111 actsvg::svg::object xyView;
0112 actsvg::svg::object zrView;
0113
0114
0115 const Acts::TrackingVolume* world = tGeometry.highestTrackingVolume();
0116 if (world != nullptr) {
0117
0118 Acts::Svg::TrackingGeometryConverter::State cState;
0119
0120
0121 Acts::Svg::TrackingGeometryConverter::convert(
0122 gctx, *world, cOptions.trackingGeometryOptions, cState);
0123
0124 xyView = Acts::Svg::group(cState.xyCrossSection,
0125 cOptions.prefix + "projection_xy");
0126 zrView = Acts::Svg::group(cState.zrCrossSection,
0127 cOptions.prefix + "projection_zr");
0128 }
0129 return {xyView, zrView};
0130 }
0131
0132
0133
0134 namespace {
0135 void convertPortalLink(const GeometryContext& gctx,
0136 const PortalLinkBase& portalLink, Direction direction,
0137 std::vector<Svg::ProtoPortal::link>& links) {
0138 auto getCenters = [&](const GridPortalLink& grid, AxisDirection axis0) {
0139 assert((grid.dim() == 2 || grid.dim() == 1) &&
0140 "Grid has unexpected number of dimension");
0141
0142 std::vector<Vector2> centers;
0143
0144 const auto localBins = grid.grid().numLocalBinsAny();
0145 if (grid.dim() == 2) {
0146 for (std::size_t i = 1; i <= localBins[0]; ++i) {
0147 for (std::size_t j = 1; j <= localBins[1]; ++j) {
0148 auto center = grid.grid().binCenterAny({i, j});
0149 assert(center.size() == 2);
0150 centers.push_back(Vector2(center[0], center[1]));
0151 }
0152 }
0153 } else {
0154 for (std::size_t i = 1; i <= localBins[0]; ++i) {
0155 auto center = grid.grid().binCenterAny({i});
0156 assert(center.size() == 1);
0157 if (axis0 == grid.direction()) {
0158 centers.push_back(Vector2(center[0], 0.));
0159 } else {
0160 centers.push_back(Vector2(0., center[0]));
0161 }
0162 }
0163 }
0164
0165 return centers;
0166 };
0167
0168 if (const auto* discBounds =
0169 dynamic_cast<const DiscBounds*>(&portalLink.surface().bounds());
0170 discBounds != nullptr) {
0171 if (const auto* grid = dynamic_cast<const GridPortalLink*>(&portalLink);
0172 grid != nullptr) {
0173 std::vector<Vector2> centers = getCenters(*grid, AxisDirection::AxisR);
0174 for (const auto& center : centers) {
0175 Svg::ProtoPortal::link link;
0176 link._start = portalLink.surface().localToGlobal(gctx, center);
0177 Vector3 normal = portalLink.surface().normal(gctx, link._start);
0178 link._end = link._start + normal * 10. * direction.sign();
0179 links.push_back(link);
0180 }
0181 } else if (const auto* composite =
0182 dynamic_cast<const CompositePortalLink*>(&portalLink);
0183 composite != nullptr) {
0184 for (const auto& link : composite->links()) {
0185 convertPortalLink(gctx, link, direction, links);
0186 }
0187 } else {
0188 double rMin = discBounds->rMin();
0189 double rMax = discBounds->rMax();
0190 double rMid = 0.5 * (rMin + rMax);
0191
0192 Svg::ProtoPortal::link link;
0193 link._start = portalLink.surface().center(gctx) + Vector3(rMid, 0., 0.);
0194 Vector3 normal = portalLink.surface().normal(gctx, link._start);
0195 link._end = link._start + normal * 10. * direction.sign();
0196 links.push_back(link);
0197 }
0198
0199 } else if (const auto* cylBounds = dynamic_cast<const CylinderBounds*>(
0200 &portalLink.surface().bounds());
0201 cylBounds != nullptr) {
0202 if (const auto* grid = dynamic_cast<const GridPortalLink*>(&portalLink);
0203 grid != nullptr) {
0204 std::vector<Vector2> centers = getCenters(*grid, AxisDirection::AxisRPhi);
0205 for (const auto& center : centers) {
0206 Svg::ProtoPortal::link link;
0207 link._start = portalLink.surface().localToGlobal(gctx, center);
0208 Vector3 normal = portalLink.surface().normal(gctx, link._start);
0209 link._end = link._start + normal * 10. * direction.sign();
0210 links.push_back(link);
0211 }
0212 } else if (const auto* composite =
0213 dynamic_cast<const CompositePortalLink*>(&portalLink);
0214 composite != nullptr) {
0215 for (const auto& link : composite->links()) {
0216 convertPortalLink(gctx, link, direction, links);
0217 }
0218 } else {
0219 double r = cylBounds->get(CylinderBounds::eR);
0220 Svg::ProtoPortal::link link;
0221 link._start = portalLink.surface().center(gctx) + Vector3(r, 0., 0.);
0222 Vector3 normal = portalLink.surface().normal(gctx, link._start);
0223 link._end = link._start + normal * 10. * direction.sign();
0224 links.push_back(link);
0225 }
0226 } else {
0227 throw std::invalid_argument("Unknown bounds type");
0228 }
0229 }
0230
0231 struct Visitor : TrackingGeometryVisitor {
0232 explicit Visitor(const GeometryContext& gctxIn) : gctx(gctxIn) {}
0233
0234 void visitSurface(const Surface& surface) override {
0235 auto proto = Svg::SurfaceConverter::convert(gctx, surface, {});
0236 surfaces.push_back(std::pair{&surface, proto});
0237 }
0238
0239 void visitPortal(const Portal& portal) override {
0240
0241 visitSurface(portal.surface());
0242
0243 auto pIt = std::ranges::find_if(
0244 portals, [&](const auto& p) { return p.first == &portal; });
0245
0246 Svg::ProtoPortal pPortal;
0247 if (pIt == portals.end()) {
0248 pPortal._name = "portal_" + std::to_string(portals.size());
0249 auto surface = Svg::SurfaceConverter::convert(gctx, portal.surface(), {});
0250 pPortal._surface = surface;
0251
0252 if (auto link = portal.getLink(Direction::AlongNormal());
0253 link != nullptr) {
0254 convertPortalLink(gctx, *link, Direction::AlongNormal(),
0255 pPortal._volume_links);
0256 }
0257
0258 if (auto link = portal.getLink(Direction::OppositeNormal());
0259 link != nullptr) {
0260 convertPortalLink(gctx, *link, Direction::OppositeNormal(),
0261 pPortal._volume_links);
0262 }
0263 portals.push_back(std::pair{&portal, pPortal});
0264 } else {
0265 pPortal = pIt->second;
0266 }
0267
0268 auto& [volume, pVolume] = volumes.back();
0269 pVolume._portals.push_back(pPortal);
0270 }
0271
0272 void visitVolume(const TrackingVolume& volume) override {
0273 Svg::ProtoVolume pVolume;
0274 pVolume._name = volume.volumeName();
0275
0276 auto volumeTransform = volume.transform();
0277
0278
0279 enum svgBv : unsigned int {
0280 rInner = 0u,
0281 rOuter = 1u,
0282 zPos = 2u,
0283 zHalf = 3u,
0284 phiSec = 4u,
0285 avgPhi = 5u,
0286 };
0287
0288 using enum CylinderVolumeBounds::BoundValues;
0289 const auto& boundValues = volume.volumeBounds().values();
0290 if (volume.volumeBounds().type() == Acts::VolumeBounds::eCylinder) {
0291 pVolume._bound_values.resize(6);
0292 pVolume._bound_values.at(svgBv::rInner) =
0293 static_cast<actsvg::scalar>(boundValues[eMinR]);
0294 pVolume._bound_values.at(svgBv::rOuter) =
0295 static_cast<actsvg::scalar>(boundValues[eMaxR]);
0296 pVolume._bound_values.at(svgBv::zPos) =
0297 static_cast<actsvg::scalar>(volumeTransform.translation().z());
0298 pVolume._bound_values.at(svgBv::zHalf) =
0299 static_cast<actsvg::scalar>(boundValues[eHalfLengthZ]);
0300 pVolume._bound_values.at(svgBv::phiSec) =
0301 static_cast<actsvg::scalar>(boundValues[eHalfPhiSector]);
0302 pVolume._bound_values.at(svgBv::avgPhi) =
0303 static_cast<actsvg::scalar>(boundValues[eAveragePhi]);
0304 } else {
0305 throw std::invalid_argument("Unknown bounds type");
0306 }
0307
0308 volumes.push_back(std::pair{&volume, pVolume});
0309 }
0310
0311 const GeometryContext& gctx;
0312
0313 std::vector<std::pair<const Portal*, Svg::ProtoPortal>> portals;
0314
0315 std::vector<std::pair<const TrackingVolume*, Svg::ProtoVolume>> volumes;
0316
0317 std::vector<std::pair<const Surface*, Svg::ProtoSurface>> surfaces;
0318 };
0319 }
0320
0321 std::vector<actsvg::svg::object> drawTrackingGeometry(
0322 const GeometryContext& gctx, const TrackingGeometry& tGeometry,
0323 std::variant<actsvg::views::x_y, actsvg::views::z_r> view,
0324 bool drawSurfaces, bool highlightMaterial) {
0325 if (tGeometry.geometryVersion() != TrackingGeometry::GeometryVersion::Gen3) {
0326 throw std::invalid_argument{
0327 "Input tracking geometry needs to have been built in Gen3 mode"};
0328 }
0329
0330 Visitor visitor(gctx);
0331 tGeometry.apply(visitor);
0332
0333 std::vector<actsvg::svg::object> objects;
0334
0335 std::visit(
0336 [&](auto& _view) {
0337 for (const auto& [tv, volume] : visitor.volumes) {
0338 std::string id = tv->volumeName();
0339 auto object = actsvg::display::volume(id, volume, _view);
0340 objects.push_back(object);
0341
0342 std::vector<std::string> lines;
0343
0344 std::stringstream ss;
0345 ss << "ID: " << tv->geometryId();
0346 lines.push_back(ss.str());
0347
0348 ss.str("");
0349 ss << tv->volumeBounds();
0350 std::string bounds = ss.str();
0351
0352 ss.str("");
0353 for (std::size_t i = 0; i < bounds.size(); ++i) {
0354 ss << bounds[i];
0355 if (ss.str().size() > 40 && bounds[i] == ' ') {
0356 lines.push_back(ss.str());
0357 ss.str("");
0358 }
0359 }
0360
0361 auto text = actsvg::draw::connected_info_box(
0362 "info_volume_" + volume._name, {0, 0}, volume._name,
0363 {{._rgb{200, 200, 200}}}, {._fc{._rgb{0, 0, 0}}, ._size = 24},
0364 lines, {{._rgb{220, 220, 220}}},
0365 {._fc{._rgb{0, 0, 0}}, ._size = 24}, {}, object);
0366
0367 objects.push_back(text);
0368 }
0369
0370 if (drawSurfaces) {
0371 for (const auto& [surface, proto] : visitor.surfaces) {
0372 std::stringstream ss;
0373 ss << surface->geometryId();
0374 auto object = actsvg::display::surface(ss.str(), proto, _view);
0375
0376 if (highlightMaterial && surface->surfaceMaterial() != nullptr) {
0377 object._stroke._sc._rgb = {255, 0, 0};
0378 object._stroke._width = 1.5;
0379 }
0380
0381 objects.push_back(object);
0382 }
0383 }
0384 },
0385 view);
0386
0387 return objects;
0388 }
0389
0390 namespace {
0391
0392 struct SurfaceArrayCollector {
0393 std::vector<std::tuple<Acts::GeometryIdentifier, const Acts::SurfaceArray*>>
0394 surfaceArrays;
0395
0396 void operator()(const Acts::TrackingVolume* tVolume) {
0397
0398 const auto& cLayers = tVolume->confinedLayers();
0399 if (cLayers != nullptr) {
0400 for (const auto& layer : cLayers->arrayObjects()) {
0401 if (layer != nullptr) {
0402 const auto& surfaceArray = layer->surfaceArray();
0403 if (surfaceArray != nullptr) {
0404 surfaceArrays.emplace_back(layer->geometryId(), surfaceArray);
0405 }
0406 }
0407 }
0408 }
0409
0410 if (const auto* policyPtr = tVolume->navigationPolicy();
0411 policyPtr != nullptr) {
0412 policyPtr->visit([&](const auto& policy) {
0413 if (auto sArrayPolicy =
0414 dynamic_cast<const Acts::SurfaceArrayNavigationPolicy*>(
0415 &policy);
0416 sArrayPolicy != nullptr) {
0417 surfaceArrays.emplace_back(tVolume->geometryId(),
0418 &sArrayPolicy->surfaceArray());
0419 }
0420 });
0421 }
0422 }
0423 };
0424 }
0425
0426
0427 std::vector<actsvg::svg::object> drawSurfaceArrays(
0428 const Acts::GeometryContext& gctx, const TrackingGeometry& tGeometry) {
0429
0430 std::vector<actsvg::svg::object> svgSurfaceArrays;
0431
0432 std::vector<const Acts::SurfaceArray*> surfaceArrays;
0433
0434
0435 SurfaceArrayCollector saCollector;
0436 tGeometry.visitVolumes(saCollector);
0437
0438 for (const auto& [geoId, surfaceArray] : saCollector.surfaceArrays) {
0439
0440 auto pIndexedSurfaceGrid =
0441 Svg::SurfaceArrayConverter::convert(gctx, *surfaceArray);
0442
0443
0444 const auto& [pSurfaces, pGrid, pAssociations] = pIndexedSurfaceGrid;
0445 std::string sArrayName =
0446 "SurfaceArray_vol" + std::to_string(geoId.volume());
0447 sArrayName += "_lay" + std::to_string(geoId.layer());
0448 if (pGrid._type == actsvg::proto::grid::e_r_phi) {
0449 svgSurfaceArrays.emplace_back(
0450 Svg::View::xy(pIndexedSurfaceGrid, "xy_" + sArrayName));
0451 } else if (pGrid._type == actsvg::proto::grid::e_z_phi) {
0452 svgSurfaceArrays.emplace_back(
0453 Svg::View::xy(pIndexedSurfaceGrid, "zphi_" + sArrayName));
0454 }
0455 }
0456 return svgSurfaceArrays;
0457 }
0458
0459 }