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