From 21a07ed14aeaae21acc6900925fb2fbf6a47db67 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Wed, 26 Aug 2026 12:27:34 +0200 Subject: [PATCH] Deduplicate the MFT flex and the shared ALPIDE metal stack This deduplicates the MFT flex printed-circuit assembly, its connector and the ALPIDE metal stack, and adds a name-based lookup for the deduplicated flex volumes. It is a follow-up on https://github.com/AliceO2Group/AliceO2/pull/15723 and extends geometry simplification ideas to MFT Flex. - The flex and its five layers were rebuilt for each of the 280 ladders, although Ladder.cxx derives the flex length from the sensor count alone, so ladders with the same sensor count get an identical flex. - Flex::makeFlex() now builds one flex per sensor-count class, guarded by a LOG(fatal) if a later call presents a different length for a class already built. - The connector pad and its PEEK box are the same solids on every flex and are now built once. - MetalStack in AlpideChip::createChip() is the same solid in every ALPIDE chip of both ITS and MFT and is now built once, guarded on its half lengths. - Every cache is dropped when gGeoManager changes, so a second geometry built in the same process cannot inherit a dangling pointer. The X7R0402 cache got the same guard. - The deduplicated volumes are named for the sensor count (flex_3, lineslayer_3, varnishlayer_3_0) instead of for half/disk/ladder. Flex::composeFlexName(), composeFlexLayerName() and getFlexVolume() resolve them, the last through gGeoManager so it also answers on a geometry read back from a file. The ladder a placement belongs to is still read from the node path. In result, an MFT-only geometry goes from 5281 to 2788 TGeoVolumes and from 32798 to 5650 TGeoNodes; ITS and MFT together from 5687 to 3187 volumes. The number of physical nodes is unchanged at 50044. --- .../ITSMFT/MFT/base/include/MFTBase/Flex.h | 29 ++- Detectors/ITSMFT/MFT/base/src/Flex.cxx | 194 +++++++++++------- .../common/simulation/src/AlpideChip.cxx | 38 +++- 3 files changed, 177 insertions(+), 84 deletions(-) diff --git a/Detectors/ITSMFT/MFT/base/include/MFTBase/Flex.h b/Detectors/ITSMFT/MFT/base/include/MFTBase/Flex.h index be9b258b44a41..114195ffb67ab 100644 --- a/Detectors/ITSMFT/MFT/base/include/MFTBase/Flex.h +++ b/Detectors/ITSMFT/MFT/base/include/MFTBase/Flex.h @@ -16,6 +16,10 @@ #ifndef ALICEO2_MFT_FLEX_H_ #define ALICEO2_MFT_FLEX_H_ +#include "Rtypes.h" + +#include + class TGeoVolume; class TGeoVolumeAssembly; @@ -34,11 +38,30 @@ class Flex TGeoVolumeAssembly* makeFlex(Int_t nbsensors, Double_t length); void makeElectricComponents(TGeoVolumeAssembly* flex, Int_t nbsensors, Double_t length, Double_t zvarnish); + /// Name of the flex shared by every ladder carrying nbsensors sensors. + /// + /// One flex is built per sensor-count class and placed on all the ladders of that class, + /// so the name is keyed by the sensor count and no longer by half/disk/ladder. The ladder + /// a given placement belongs to is still read from the node path, for example + /// /cave_1/barrel_1/MFT_0/MFT_H_0_0/MFT_D_0_0_0/MFT_L_0_0_5_5/flex_3_1 + static std::string composeFlexName(Int_t nbsensors); + + /// Name of one layer inside that flex: "lineslayer", "alulayer", "kaptonlayer" or + /// "varnishlayer". The varnish is placed twice, iflag 0 in front of the cold plate and + /// iflag 1 outside; the other layers take no iflag. + static std::string composeFlexLayerName(const char* layer, Int_t nbsensors, Int_t iflag = -1); + + /// The flex volume of that class in the current geometry, or nullptr if it has none. + /// Resolved by name, so it also answers on a geometry read back from a file. To go from + /// a ladder to its flex, take the sensor count from + /// GeometryTGeo::getNumberOfSensorsPerLadder(half, disk, ladder) and pass it here. + static TGeoVolumeAssembly* getFlexVolume(Int_t nbsensors); + private: TGeoVolume* makeLines(Int_t nbsensors, Double_t length, Double_t width, Double_t thickness); - TGeoVolume* makeAGNDandDGND(Double_t length, Double_t width, Double_t thickness); - TGeoVolume* makeKapton(Double_t length, Double_t width, Double_t thickness); - TGeoVolume* makeVarnish(Double_t length, Double_t width, Double_t thickness, Int_t iflag); + TGeoVolume* makeAGNDandDGND(Int_t nbsensors, Double_t length, Double_t width, Double_t thickness); + TGeoVolume* makeKapton(Int_t nbsensors, Double_t length, Double_t width, Double_t thickness); + TGeoVolume* makeVarnish(Int_t nbsensors, Double_t length, Double_t width, Double_t thickness, Int_t iflag); TGeoVolumeAssembly* makeElectricComponent(Double_t dx, Double_t dy, Double_t dz, Int_t iflag); Double_t* mFlexOrigin; diff --git a/Detectors/ITSMFT/MFT/base/src/Flex.cxx b/Detectors/ITSMFT/MFT/base/src/Flex.cxx index cef7446d7d30b..5922b093936cb 100644 --- a/Detectors/ITSMFT/MFT/base/src/Flex.cxx +++ b/Detectors/ITSMFT/MFT/base/src/Flex.cxx @@ -25,6 +25,9 @@ #include +#include +#include + #include "MFTBase/LadderSegmentation.h" #include "MFTBase/ChipSegmentation.h" #include "MFTBase/Flex.h" @@ -52,34 +55,77 @@ Flex::Flex(LadderSegmentation* ladder) : mFlexOrigin(), mLadderSeg(ladder) // Constructor } +//_____________________________________________________________________________ +std::string Flex::composeFlexName(Int_t nbsensors) { return "flex_" + std::to_string(nbsensors); } + +//_____________________________________________________________________________ +std::string Flex::composeFlexLayerName(const char* layer, Int_t nbsensors, Int_t iflag) +{ + std::string name = std::string(layer) + "_" + std::to_string(nbsensors); + if (iflag >= 0) { + name += "_" + std::to_string(iflag); + } + return name; +} + +//_____________________________________________________________________________ +TGeoVolumeAssembly* Flex::getFlexVolume(Int_t nbsensors) +{ + if (!gGeoManager) { + return nullptr; + } + return dynamic_cast(gGeoManager->GetVolume(composeFlexName(nbsensors).c_str())); +} + //_____________________________________________________________________________ TGeoVolumeAssembly* Flex::makeFlex(Int_t nbsensors, Double_t length) { // Informations from the technical report mft_flex_proto_5chip_v08_laz50p.docx on MFT twiki and private communications - // For the naming + // Ladders carrying the same number of sensors get the same flex: Ladder.cxx derives the + // flex length from that count alone, and nothing built below depends on which ladder asked + // for it. MFT has four such classes, so build one flex per class and place it on every + // ladder of that class instead of building 280 identical copies. + static TGeoManager* cacheOwner = nullptr; + static std::map flexPerClass; + static std::map lengthPerClass; + if (cacheOwner != gGeoManager) { + // a new geometry leaves every cached pointer dangling + cacheOwner = gGeoManager; + flexPerClass.clear(); + lengthPerClass.clear(); + } + auto cached = flexPerClass.find(nbsensors); + if (cached != flexPerClass.end()) { + if (length != lengthPerClass[nbsensors]) { + LOG(fatal) << "Flex::makeFlex: the flex for " << nbsensors << " sensors was built with length " + << lengthPerClass[nbsensors] << " but this call asks for " << length + << " - the per-class flex cache assumes the length follows the sensor count"; + } + return cached->second; + } + Geometry* mftGeom = Geometry::instance(); - Int_t idHalfMFT = mftGeom->getHalfID(mLadderSeg->GetUniqueID()); - Int_t idHalfDisk = mftGeom->getDiskID(mLadderSeg->GetUniqueID()); - Int_t idLadder = mftGeom->getLadderID(mLadderSeg->GetUniqueID()); + LOG(debug) << "Flex::makeFlex: building the flex for " << nbsensors << " sensors, first asked for by ladder " + << mftGeom->getHalfID(mLadderSeg->GetUniqueID()) << "/" + << mftGeom->getDiskID(mLadderSeg->GetUniqueID()) << "/" + << mftGeom->getLadderID(mLadderSeg->GetUniqueID()); - // First a global pointer for the flex - TGeoMedium* kMedAir = gGeoManager->GetMedium("MFT_Air$"); - auto* flex = new TGeoVolumeAssembly(Form("flex_%d_%d_%d", idHalfMFT, idHalfDisk, idLadder)); + auto* flex = new TGeoVolumeAssembly(composeFlexName(nbsensors).c_str()); // Defining one single layer for the strips and the AVDD and DVDD TGeoVolume* lines = makeLines(nbsensors, length - Geometry::sClearance, Geometry::sFlexHeight - Geometry::sClearance, Geometry::sAluThickness); // AGND and DGND layers - TGeoVolume* agnd_dgnd = makeAGNDandDGND(length - Geometry::sClearance, Geometry::sFlexHeight - Geometry::sClearance, - Geometry::sAluThickness); + TGeoVolume* agnd_dgnd = makeAGNDandDGND(nbsensors, length - Geometry::sClearance, + Geometry::sFlexHeight - Geometry::sClearance, Geometry::sAluThickness); // The others layers - TGeoVolume* kaptonlayer = makeKapton(length, Geometry::sFlexHeight, Geometry::sKaptonThickness); - TGeoVolume* varnishlayerIn = makeVarnish(length, Geometry::sFlexHeight, Geometry::sVarnishThickness, 0); - TGeoVolume* varnishlayerOut = makeVarnish(length, Geometry::sFlexHeight, Geometry::sVarnishThickness, 1); + TGeoVolume* kaptonlayer = makeKapton(nbsensors, length, Geometry::sFlexHeight, Geometry::sKaptonThickness); + TGeoVolume* varnishlayerIn = makeVarnish(nbsensors, length, Geometry::sFlexHeight, Geometry::sVarnishThickness, 0); + TGeoVolume* varnishlayerOut = makeVarnish(nbsensors, length, Geometry::sFlexHeight, Geometry::sVarnishThickness, 1); // Final flex building Double_t zvarnishIn = Geometry::sKaptonThickness / 2 + Geometry::sAluThickness + Geometry::sVarnishThickness / 2 - @@ -102,6 +148,9 @@ TGeoVolumeAssembly* Flex::makeFlex(Int_t nbsensors, Double_t length) makeElectricComponents(flex, nbsensors, length, zvarnishOut); + flexPerClass[nbsensors] = flex; + lengthPerClass[nbsensors] = length; + return flex; } @@ -178,27 +227,52 @@ void Flex::makeElectricComponents(TGeoVolumeAssembly* flex, Int_t nbsensors, Dou */ //-------------------------- New Connector ---------------------- - TGeoMedium* kMedAlu = gGeoManager->GetMedium("MFT_Alu$"); - TGeoMedium* kMedPeek = gGeoManager->GetMedium("MFT_PEEK$"); - - auto* connect = new TGeoBBox("connect", Geometry::sConnectorLength / 2, Geometry::sConnectorWidth / 2, - Geometry::sConnectorHeight / 2); - auto* remov = - new TGeoBBox("remov", Geometry::sConnectorLength / 2, Geometry::sConnectorWidth / 2 + Geometry::sEpsilon, - Geometry::sConnectorHeight / 2 + Geometry::sEpsilon); - - auto* t1 = new TGeoTranslation("t1", Geometry::sConnectorThickness, 0., -0.01); - auto* connecto = new TGeoSubtraction(connect, remov, nullptr, t1); - auto* connector = new TGeoCompositeShape("connector", connecto); - auto* connectord = new TGeoVolume("connectord", connector, kMedAlu); - connectord->SetVisibility(kTRUE); - connectord->SetLineColor(kRed); - connectord->SetLineWidth(1); - connectord->SetFillColor(connectord->GetLineColor()); - connectord->SetFillStyle(4000); // 0% transparent - - Double_t interspace = 0.1; // interspace inside the 2 ranges of connector pads - Double_t step = 0.04; // interspace between each pad inside the connector + Double_t interspace = 0.1; // interspace inside the 2 ranges of connector pads + Double_t step = 0.04; // interspace between each pad inside the connector + Double_t boxthickness = 0.05; // wall thickness of the PEEK box around the pads + + // The connector pad and the PEEK box around it are the same solids on every flex - their + // dimensions come from Geometry constants only - so build them on the first call and place + // the same two volumes on every flex afterwards. + static TGeoManager* connectorCacheOwner = nullptr; + static TGeoVolume* connectord = nullptr; + static TGeoVolume* boxconnectord = nullptr; + if (connectorCacheOwner != gGeoManager) { + // a new geometry leaves every cached pointer dangling + connectorCacheOwner = gGeoManager; + connectord = nullptr; + boxconnectord = nullptr; + } + + if (!connectord) { + TGeoMedium* kMedAlu = gGeoManager->GetMedium("MFT_Alu$"); + TGeoMedium* kMedPeek = gGeoManager->GetMedium("MFT_PEEK$"); + + auto* connect = new TGeoBBox("connect", Geometry::sConnectorLength / 2, Geometry::sConnectorWidth / 2, + Geometry::sConnectorHeight / 2); + auto* remov = + new TGeoBBox("remov", Geometry::sConnectorLength / 2, Geometry::sConnectorWidth / 2 + Geometry::sEpsilon, + Geometry::sConnectorHeight / 2 + Geometry::sEpsilon); + + auto* t1 = new TGeoTranslation("t1", Geometry::sConnectorThickness, 0., -0.01); + auto* connecto = new TGeoSubtraction(connect, remov, nullptr, t1); + auto* connector = new TGeoCompositeShape("connector", connecto); + connectord = new TGeoVolume("connectord", connector, kMedAlu); + connectord->SetVisibility(kTRUE); + connectord->SetLineColor(kRed); + connectord->SetLineWidth(1); + connectord->SetFillColor(connectord->GetLineColor()); + connectord->SetFillStyle(4000); // 0% transparent + + auto* boxconnect = new TGeoBBox("boxconnect", (2 * Geometry::sConnectorThickness + interspace + boxthickness) / 2, + Geometry::sFlexHeight / 2 - 0.04, Geometry::sConnectorHeight / 2); + auto* boxremov = new TGeoBBox("boxremov", (2 * Geometry::sConnectorThickness + interspace) / 2, + (Geometry::sFlexHeight - 0.1 - step) / 2, Geometry::sConnectorHeight / 2 + 0.001); + auto* boxconnecto = new TGeoSubtraction(boxconnect, boxremov, nullptr, nullptr); + auto* boxconnector = new TGeoCompositeShape("boxconnector", boxconnecto); + boxconnectord = new TGeoVolume("boxconnectord", boxconnector, kMedPeek); + } + for (Int_t id = 0; id < 37; id++) { flex->AddNode( connectord, id + total, @@ -212,14 +286,6 @@ void Flex::makeElectricComponents(TGeoVolumeAssembly* flex, Int_t nbsensors, Dou flex->AddNode(connectord, id + total + 37, transformationpi); } - Double_t boxthickness = 0.05; - auto* boxconnect = new TGeoBBox("boxconnect", (2 * Geometry::sConnectorThickness + interspace + boxthickness) / 2, - Geometry::sFlexHeight / 2 - 0.04, Geometry::sConnectorHeight / 2); - auto* boxremov = new TGeoBBox("boxremov", (2 * Geometry::sConnectorThickness + interspace) / 2, - (Geometry::sFlexHeight - 0.1 - step) / 2, Geometry::sConnectorHeight / 2 + 0.001); - auto* boxconnecto = new TGeoSubtraction(boxconnect, boxremov, nullptr, nullptr); - auto* boxconnector = new TGeoCompositeShape("boxconnector", boxconnecto); - auto* boxconnectord = new TGeoVolume("boxconnectord", boxconnector, kMedPeek); flex->AddNode(boxconnectord, 1, new TGeoTranslation(length / 2 - Geometry::sConnectorOffset, -step / 2, zvarnish - Geometry::sVarnishThickness / 2 - Geometry::sConnectorHeight / 2 - @@ -230,16 +296,18 @@ void Flex::makeElectricComponents(TGeoVolumeAssembly* flex, Int_t nbsensors, Dou TGeoVolumeAssembly* Flex::makeElectricComponent(Double_t dx, Double_t dy, Double_t dz, Int_t id) { - Geometry* mftGeom = Geometry::instance(); - Int_t idHalfMFT = mftGeom->getHalfID(mLadderSeg->GetUniqueID()); - Int_t idHalfDisk = mftGeom->getDiskID(mLadderSeg->GetUniqueID()); - Int_t idLadder = mftGeom->getLadderID(mLadderSeg->GetUniqueID()); //------------------------------------------------------ // X7R0402 (and its capacitor/welding0/welding1 children) is geometrically identical at // every call site (dx,dy,dz are always Geometry::sCapacitorDy/Dx/Dz) — build the whole // assembly once, place it many times. + static TGeoManager* cacheOwner = nullptr; static TGeoVolumeAssembly* X7R0402 = nullptr; static Double_t sCachedDx = 0., sCachedDy = 0., sCachedDz = 0.; + if (cacheOwner != gGeoManager) { + // a new geometry leaves the cached pointer dangling + cacheOwner = gGeoManager; + X7R0402 = nullptr; + } if (X7R0402) { if (dx != sCachedDx || dy != sCachedDy || dz != sCachedDz) { LOG(fatal) << "Flex::makeElectricComponent: cached X7R0402 assembly was built with " @@ -400,15 +468,10 @@ TGeoVolume* Flex::makeLines(Int_t nbsensors, Double_t length, Double_t widthflex kTotalLinesNb++; } - Geometry* mftGeom = Geometry::instance(); - Int_t idHalfMFT = mftGeom->getHalfID(mLadderSeg->GetUniqueID()); - Int_t idHalfDisk = mftGeom->getDiskID(mLadderSeg->GetUniqueID()); - Int_t idLadder = mftGeom->getLadderID(mLadderSeg->GetUniqueID()); - TGeoMedium* kMedAlu = gGeoManager->GetMedium("MFT_Alu$"); - auto* lineslayer = - new TGeoVolume(Form("lineslayer_%d_%d_%d", idHalfMFT, idHalfDisk, idLadder), layern[kTotalLinesNb - 1], kMedAlu); + auto* lineslayer = new TGeoVolume(composeFlexLayerName("lineslayer", nbsensors).c_str(), + layern[kTotalLinesNb - 1], kMedAlu); lineslayer->SetVisibility(true); lineslayer->SetLineColor(kBlue); @@ -416,7 +479,7 @@ TGeoVolume* Flex::makeLines(Int_t nbsensors, Double_t length, Double_t widthflex } //_____________________________________________________________________________ -TGeoVolume* Flex::makeAGNDandDGND(Double_t length, Double_t widthflex, Double_t thickness) +TGeoVolume* Flex::makeAGNDandDGND(Int_t nbsensors, Double_t length, Double_t widthflex, Double_t thickness) { // AGND and DGND layers @@ -464,13 +527,8 @@ TGeoVolume* Flex::makeAGNDandDGND(Double_t length, Double_t widthflex, Double_t //-------------- - Geometry* mftGeom = Geometry::instance(); - Int_t idHalfMFT = mftGeom->getHalfID(mLadderSeg->GetUniqueID()); - Int_t idHalfDisk = mftGeom->getDiskID(mLadderSeg->GetUniqueID()); - Int_t idLadder = mftGeom->getLadderID(mLadderSeg->GetUniqueID()); - TGeoMedium* kMedAlu = gGeoManager->GetMedium("MFT_Alu$"); - auto* alulayer = new TGeoVolume(Form("alulayer_%d_%d_%d", idHalfMFT, idHalfDisk, idLadder), layern[2], kMedAlu); + auto* alulayer = new TGeoVolume(composeFlexLayerName("alulayer", nbsensors).c_str(), layern[2], kMedAlu); alulayer->SetVisibility(true); alulayer->SetLineColor(kBlue); @@ -478,7 +536,7 @@ TGeoVolume* Flex::makeAGNDandDGND(Double_t length, Double_t widthflex, Double_t } //_____________________________________________________________________________ -TGeoVolume* Flex::makeKapton(Double_t length, Double_t widthflex, Double_t thickness) +TGeoVolume* Flex::makeKapton(Int_t nbsensors, Double_t length, Double_t widthflex, Double_t thickness) { auto* layer = new TGeoBBox("layer", length / 2, widthflex / 2, thickness / 2); @@ -494,14 +552,9 @@ TGeoVolume* Flex::makeKapton(Double_t length, Double_t widthflex, Double_t thick auto* layerholesub2 = new TGeoSubtraction(layerhole1, hole2, nullptr, t2); auto* layerhole2 = new TGeoCompositeShape("layerhole2", layerholesub2); - Geometry* mftGeom = Geometry::instance(); - Int_t idHalfMFT = mftGeom->getHalfID(mLadderSeg->GetUniqueID()); - Int_t idHalfDisk = mftGeom->getDiskID(mLadderSeg->GetUniqueID()); - Int_t idLadder = mftGeom->getLadderID(mLadderSeg->GetUniqueID()); - TGeoMedium* kMedKapton = gGeoManager->GetMedium("MFT_Kapton$"); auto* kaptonlayer = - new TGeoVolume(Form("kaptonlayer_%d_%d_%d", idHalfMFT, idHalfDisk, idLadder), layerhole2, kMedKapton); + new TGeoVolume(composeFlexLayerName("kaptonlayer", nbsensors).c_str(), layerhole2, kMedKapton); kaptonlayer->SetVisibility(true); kaptonlayer->SetLineColor(kYellow); @@ -509,7 +562,7 @@ TGeoVolume* Flex::makeKapton(Double_t length, Double_t widthflex, Double_t thick } //_____________________________________________________________________________ -TGeoVolume* Flex::makeVarnish(Double_t length, Double_t widthflex, Double_t thickness, Int_t iflag) +TGeoVolume* Flex::makeVarnish(Int_t nbsensors, Double_t length, Double_t widthflex, Double_t thickness, Int_t iflag) { auto* layer = new TGeoBBox("layer", length / 2, widthflex / 2, thickness / 2); @@ -525,16 +578,11 @@ TGeoVolume* Flex::makeVarnish(Double_t length, Double_t widthflex, Double_t thic auto* layerholesub2 = new TGeoSubtraction(layerhole1, hole2, nullptr, t2); auto* layerhole2 = new TGeoCompositeShape("layerhole2", layerholesub2); - Geometry* mftGeom = Geometry::instance(); - Int_t idHalfMFT = mftGeom->getHalfID(mLadderSeg->GetUniqueID()); - Int_t idHalfDisk = mftGeom->getDiskID(mLadderSeg->GetUniqueID()); - Int_t idLadder = mftGeom->getLadderID(mLadderSeg->GetUniqueID()); - TGeoMedium* kMedVarnish = gGeoManager->GetMedium("MFT_Epoxy$"); // we assume that varnish = epoxy ... TGeoMaterial* kMatVarnish = kMedVarnish->GetMaterial(); // kMatVarnish->Dump(); auto* varnishlayer = - new TGeoVolume(Form("varnishlayer_%d_%d_%d_%d", idHalfMFT, idHalfDisk, idLadder, iflag), layerhole2, kMedVarnish); + new TGeoVolume(composeFlexLayerName("varnishlayer", nbsensors, iflag).c_str(), layerhole2, kMedVarnish); varnishlayer->SetVisibility(true); varnishlayer->SetLineColor(kGreen - 1); diff --git a/Detectors/ITSMFT/common/simulation/src/AlpideChip.cxx b/Detectors/ITSMFT/common/simulation/src/AlpideChip.cxx index 4d79fc77f46ec..b388c3c16b8e4 100644 --- a/Detectors/ITSMFT/common/simulation/src/AlpideChip.cxx +++ b/Detectors/ITSMFT/common/simulation/src/AlpideChip.cxx @@ -77,9 +77,9 @@ TGeoVolume* AlpideChip::createChip(const Double_t ychip, // The sensor TGeoBBox* sensor = new TGeoBBox(xchip, ylen, zchip); - // The metal layer + // The metal layer. Its three half lengths come from SegmentationAlpide constants only, so + // it is the same solid in every ALPIDE chip of both ITS and MFT; see the cache below. ylen = 0.5 * sMetalLayerThick; - TGeoBBox* metallay = new TGeoBBox(xchip, ylen, zchip); // We have all shapes: now create the real volumes TGeoMedium* medSi = mgr->GetMedium("ALPIDE_SI$"); @@ -114,12 +114,34 @@ TGeoVolume* AlpideChip::createChip(const Double_t ychip, sensVol->SetFillColor(sensVol->GetLineColor()); sensVol->SetFillStyle(4000); // 0% transparent - TGeoVolume* metalVol = new TGeoVolume("MetalStack", metallay, medMetal); - metalVol->SetVisibility(kTRUE); - metalVol->SetLineColor(1); - metalVol->SetLineWidth(1); - metalVol->SetFillColor(metalVol->GetLineColor()); - metalVol->SetFillStyle(4000); // 0% transparent + // Every chip carries the same metal stack, so build it on the first call and place that one + // volume in all the others. It sits at a different height in each chip, but that is the + // node's translation below and not a property of the volume. + static TGeoManager* metalCacheOwner = nullptr; + static TGeoVolume* metalVol = nullptr; + if (metalCacheOwner != gGeoManager) { + // a new geometry leaves the cached pointer dangling + metalCacheOwner = gGeoManager; + metalVol = nullptr; + } + + if (!metalVol) { + TGeoBBox* metallay = new TGeoBBox(xchip, ylen, zchip); + metalVol = new TGeoVolume("MetalStack", metallay, medMetal); + metalVol->SetVisibility(kTRUE); + metalVol->SetLineColor(1); + metalVol->SetLineWidth(1); + metalVol->SetFillColor(metalVol->GetLineColor()); + metalVol->SetFillStyle(4000); // 0% transparent + } + + TGeoBBox* metallay = (TGeoBBox*)metalVol->GetShape(); + if (metallay->GetDX() != xchip || metallay->GetDY() != ylen || metallay->GetDZ() != zchip) { + LOG(fatal) << "AlpideChip::createChip: the cached MetalStack was built with half lengths " + << metallay->GetDX() << "," << metallay->GetDY() << "," << metallay->GetDZ() + << " but this call asks for " << xchip << "," << ylen << "," << zchip + << " - the single-volume cache assumes the same metal stack in every chip"; + } // Now build up the chip ypos = chip->GetDY() - metallay->GetDY();