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