File indexing completed on 2025-10-30 07:54:53
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsPlugins/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/Utilities/Zip.hpp"
0019 #include "ActsPlugins/ActSVG/DetectorVolumeSvgConverter.hpp"
0020 #include "ActsPlugins/ActSVG/PortalSvgConverter.hpp"
0021 #include "ActsPlugins/ActSVG/SurfaceArraySvgConverter.hpp"
0022 #include "ActsPlugins/ActSVG/SurfaceSvgConverter.hpp"
0023
0024 #include <sstream>
0025 #include <stdexcept>
0026
0027 using namespace Acts;
0028
0029 namespace ActsPlugins::Svg {
0030
0031 std::vector<actsvg::svg::object> TrackingGeometryConverter::convert(
0032 const GeometryContext& gctx, const TrackingGeometry& tGeometry,
0033 const TrackingGeometryConverter::Options& cOptions) {
0034
0035 const TrackingVolume* world = tGeometry.highestTrackingVolume();
0036
0037
0038 TrackingGeometryConverter::State cState;
0039
0040
0041 convert(gctx, *world, cOptions, cState);
0042
0043
0044 std::vector<actsvg::svg::object> finalViews = cState.finalViews;
0045 if (!cState.xyCrossSection.empty()) {
0046 finalViews.push_back(
0047 group(cState.xyCrossSection, cOptions.prefix + "layers_xy"));
0048 }
0049 if (!cState.zrCrossSection.empty()) {
0050 finalViews.push_back(
0051 group(cState.zrCrossSection, cOptions.prefix + "layers_zr"));
0052 }
0053
0054 return finalViews;
0055 }
0056
0057 void TrackingGeometryConverter::convert(
0058 const GeometryContext& gctx, const TrackingVolume& tVolume,
0059 const TrackingGeometryConverter::Options& cOptions,
0060 TrackingGeometryConverter::State& cState) {
0061
0062 if (tVolume.confinedLayers() != nullptr) {
0063 for (const auto& layer : tVolume.confinedLayers()->arrayObjects()) {
0064 if (layer->surfaceArray() != nullptr) {
0065 GeometryIdentifier geoID = layer->geometryId();
0066 std::string layerName = cOptions.prefix + "vol_" +
0067 std::to_string(geoID.volume()) + "_layer_" +
0068 std::to_string(geoID.layer());
0069
0070 LayerConverter::Options lOptions;
0071
0072 auto deflOptions = cOptions.layerOptions.find(geoID);
0073 if (deflOptions != cOptions.layerOptions.end()) {
0074 lOptions = (*deflOptions);
0075 lOptions.name = layerName;
0076 }
0077
0078 auto layerSheets = LayerConverter::convert(gctx, *layer, lOptions);
0079
0080
0081 for (const auto& lSheet : layerSheets) {
0082 if (lSheet.is_defined()) {
0083 cState.finalViews.push_back(lSheet);
0084 }
0085 }
0086
0087 if (layerSheets[LayerConverter::eCrossSectionXY].is_defined() &&
0088 layer->surfaceRepresentation().type() == Surface::Cylinder) {
0089 cState.xyCrossSection.push_back(
0090 layerSheets[LayerConverter::eCrossSectionXY]);
0091 }
0092
0093 if (layerSheets[LayerConverter::eCrossSectionZR].is_defined()) {
0094 cState.zrCrossSection.push_back(
0095 layerSheets[LayerConverter::eCrossSectionZR]);
0096 }
0097 }
0098 }
0099 }
0100
0101
0102 if (tVolume.confinedVolumes() != nullptr) {
0103 for (const auto& volume : tVolume.confinedVolumes()->arrayObjects()) {
0104 convert(gctx, *volume, cOptions, cState);
0105 }
0106 }
0107 }
0108
0109 std::array<actsvg::svg::object, 2> TrackingGeometryProjections::convert(
0110 const GeometryContext& gctx, const TrackingGeometry& tGeometry,
0111 const TrackingGeometryProjections::Options& cOptions) {
0112
0113 actsvg::svg::object xyView;
0114 actsvg::svg::object zrView;
0115
0116
0117 const TrackingVolume* world = tGeometry.highestTrackingVolume();
0118 if (world != nullptr) {
0119
0120 ActsPlugins::Svg::TrackingGeometryConverter::State cState;
0121
0122
0123 ActsPlugins::Svg::TrackingGeometryConverter::convert(
0124 gctx, *world, cOptions.trackingGeometryOptions, cState);
0125
0126 xyView = group(cState.xyCrossSection, cOptions.prefix + "projection_xy");
0127 zrView = group(cState.zrCrossSection, 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() == 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<GeometryIdentifier, const SurfaceArray*>>
0394 surfaceArrays;
0395
0396 void operator()(const 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 SurfaceArrayNavigationPolicy*>(&policy);
0415 sArrayPolicy != nullptr) {
0416 surfaceArrays.emplace_back(tVolume->geometryId(),
0417 &sArrayPolicy->surfaceArray());
0418 }
0419 });
0420 }
0421 }
0422 };
0423 }
0424
0425
0426 std::vector<actsvg::svg::object> drawSurfaceArrays(
0427 const GeometryContext& gctx, const TrackingGeometry& tGeometry) {
0428
0429 std::vector<actsvg::svg::object> svgSurfaceArrays;
0430
0431 std::vector<const SurfaceArray*> surfaceArrays;
0432
0433
0434 SurfaceArrayCollector saCollector;
0435 tGeometry.visitVolumes(saCollector);
0436
0437 for (const auto& [geoId, surfaceArray] : saCollector.surfaceArrays) {
0438
0439 auto pIndexedSurfaceGrid =
0440 Svg::SurfaceArrayConverter::convert(gctx, *surfaceArray);
0441
0442
0443 const auto& [pSurfaces, pGrid, pAssociations] = pIndexedSurfaceGrid;
0444 std::string sArrayName =
0445 "SurfaceArray_vol" + std::to_string(geoId.volume());
0446 sArrayName += "_lay" + std::to_string(geoId.layer());
0447 if (pGrid._type == actsvg::proto::grid::e_r_phi) {
0448 svgSurfaceArrays.emplace_back(
0449 Svg::View::xy(pIndexedSurfaceGrid, "xy_" + sArrayName));
0450 } else if (pGrid._type == actsvg::proto::grid::e_z_phi) {
0451 svgSurfaceArrays.emplace_back(
0452 Svg::View::xy(pIndexedSurfaceGrid, "zphi_" + sArrayName));
0453 }
0454 }
0455 return svgSurfaceArrays;
0456 }
0457
0458 }