diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h index a4357bf8b8995..e85c610d65d0a 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -57,6 +57,12 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Draw reference circles at inner and outer radius of stave layer, for visualisation bool drawReferenceCircles = false; + // Copper per plane [cm] in the disk end-of-stave cards; drives the card x/X0. + // Defaults match o2::ft3::ModuleConstants::eosCard{OT,ML}.copperThickness. + float ft3EosCardCuThicknessOT = 0.0122f; // Large (OT) disks + float ft3EosCardCuThicknessML = 0.0122f; // Small (ML) disks + bool addDiskEosCards = true; // add EoS cards to the stave-geometry disks + O2ParamDef(FT3BaseParam, "FT3Base"); }; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h index 06752bf267db1..527b4fa7e2f25 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h @@ -49,7 +49,8 @@ enum class MaterialID : unsigned { Epoxy, Aluminum, Foam, - Water + Water, + FR4 }; // Transport parameters of a medium, in the order expected by Detector::Medium() @@ -108,7 +109,10 @@ inline const std::unordered_map materials = { // Carbon foam core of the disk separation layer {MaterialID::Foam, {"Foam", kBlack, 0.17f, 0.0f, 0.0f, 0, {12.0107f}, {6.0f}, {}, passiveTracking}}, // Coolant inside the kapton pipes - {MaterialID::Water, {"Water", kBlue, 1.064f, 0.0f, 0.0f, 0, {18.01528f}, {8.0f}, {}, passiveTracking}}}; + {MaterialID::Water, {"Water", kBlue, 1.064f, 0.0f, 0.0f, 0, {18.01528f}, {8.0f}, {}, passiveTracking}}, + // FR4 board of the disk end-of-stave cards: 60% glass (SiO2) + 40% epoxy by + // weight -> Si, O, C, H ; rho = 1.85 g/cm3 (same recipe as the TRK barrel) + {MaterialID::FR4, {"FR4", kGreen + 3, 1.85f, 0.0f, 0.0f, 4, {28.0855f, 15.9994f, 12.0107f, 1.00794f}, {14.0f, 8.0f, 6.0f, 1.0f}, {0.2804f, 0.3836f, 0.3040f, 0.0320f}, passiveTracking}}}; // The inactive rim of a sensor is made of silicon as well, but is drawn // separately so that it can be told apart from the active area. const int SiInactiveColor = kRed; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h index e071e5cbd521b..13f1a45981e22 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -84,6 +84,14 @@ class FT3Module double z_offset_to_carbon_face, std::pair& absAllowedYRange, double y_midpoint, bool mirrorStaveAroundX, unsigned* staveVolumeCount); + // FR4 + Cu end-of-stave card at the outer-radius tip of a disk stave. One card + // per stave tip, placed downstream of the stave (away from the IP), in front of + // the connection disk. Dimensions from Constants::getEosCardParams(isML). + void addEndOfStaveCard( + TGeoVolume* motherVolume, const std::string& name, int direction, + unsigned volume_count, double x_mid, double y_tip, double z_sensor, + bool isML, double cuThickness); + // Helper functions void fill_stave_greedy( PosNegPositionTypes& y_positions, unsigned kSensorStack, diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index c14d5fd42d719..6dc265fd3e030 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -309,6 +309,39 @@ inline StaveConfig getStaveConfig(bool isInnerDisk) } } +// --------------------------------------------------------------------------- +// End-of-stave (EoS) readout cards at the outer-radius tips of the disk staves. +// Same construction as the TRK barrel cards: an FR4 board carrying evenly spaced +// copper planes. Separate parameter sets for the Small (ML/inner) and Large (OT) +// disks. All lengths in cm. Local card axes at the outer stave tip: +// length -> along beam z, width -> along stave width, thickness -> stave axis. +struct EosCardParams { + double length; // along beam z + double width; // along stave width + double thickness; // along stave axis (radial at the tip): FR4 + Cu planes + int nCopperLayers; // copper planes, spread over the thickness + double copperThickness; // per copper plane (default of the FT3Base Cu knob) + double zGap; // clearance from the stave's downstream face +}; +// NOTE on width: a card spanning the full z-range must be narrower than the +// clearance to the staggered neighbour stave, whose carbon triangle is +// staveWidth/2 (= 2.61 cm) half-wide: half-width < x_midpoint_spacing - staveWidth/2. +// That is tightest on the ML disks (4.5 - 2.61 = 1.89 cm), so width = 3.4 cm +// (half 1.7 cm) clears both ML and OT (OT spacing 4.92 cm) neighbours. +// Large (OT) disks +constexpr EosCardParams eosCardOT{12.0, 3.4, 0.15, 4, 0.0122, 0.2}; +// Small (ML) disks +constexpr EosCardParams eosCardML{8.0, 3.4, 0.15, 4, 0.0122, 0.2}; +inline const EosCardParams& getEosCardParams(bool isML) { return isML ? eosCardML : eosCardOT; } + +// Downstream half-length (beam z) that the disk layer envelope must gain to +// contain the EoS cards (card body + clearance + a small margin). +inline double eosCardEnvelopeExtension(bool isML) +{ + const EosCardParams& c = getEosCardParams(isML); + return c.zGap + c.length + 0.2; +} + } // namespace o2::ft3::ModuleConstants #endif // FT3MODULECONSTANTS_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx index b70afc9337ab9..48cf5428fab38 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx @@ -427,9 +427,26 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) double innerRadiusForAirTube = mIsMiddleLayer ? mInnerRadius : mInnerRadius - 9.0; double outerRadiusForAirTube = mIsMiddleLayer ? mOuterRadius + 2.5 : mOuterRadius + 3.0; // MvL: try 70.5 // 2.5 cm tolerance instead - TGeoTube* layer = new TGeoTube(innerRadiusForAirTube - 0.2, outerRadiusForAirTube, - z_layer_thickness / 2); - layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + const double rInEnv = innerRadiusForAirTube - 0.2; + const double rOutEnv = outerRadiusForAirTube; + const double H0 = z_layer_thickness / 2; + if (ft3Params.addDiskEosCards) { + // Extend the envelope DOWNSTREAM only (away from the IP) with a union, so the + // end-of-stave cards fit without growing toward the IP / the neighbouring disk. + const double ext = o2::ft3::ModuleConstants::eosCardEnvelopeExtension(mIsMiddleLayer); + const double s = (mDirection == 1) ? 1.0 : -1.0; // downstream sign + TGeoTube* baseTube = new TGeoTube((mLayerName + "_base").c_str(), rInEnv, rOutEnv, H0); + TGeoTube* extTube = new TGeoTube((mLayerName + "_eosext").c_str(), rInEnv, rOutEnv, ext / 2); + TGeoTranslation* extTr = new TGeoTranslation((mLayerName + "_eosext_tr").c_str(), 0, 0, s * (H0 + ext / 2)); + extTr->RegisterYourself(); + TGeoCompositeShape* layerShape = new TGeoCompositeShape( + (mLayerName + "_env").c_str(), + Form("%s + %s:%s", baseTube->GetName(), extTube->GetName(), extTr->GetName())); + layerVol = new TGeoVolume(mLayerName.c_str(), layerShape, medAir); + } else { + TGeoTube* layer = new TGeoTube(rInEnv, rOutEnv, H0); + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + } if (ft3Params.drawReferenceCircles) { std::string referenceCirclesName = "ReferenceCircles_Dir" + std::to_string(mDirection) + "_Layer" + std::to_string(mLayerNumber); diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx index aebab49ba9dcf..abeff29569096 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx @@ -479,6 +479,50 @@ bool staveMidpointAndMirror(const Constants::StaveConfig& staveConfig, int stave return true; } +/* + * FR4 + Cu end-of-stave card at the outer-radius tip of a disk stave. + * Mirrors the TRK barrel card: an FR4 board with evenly spaced copper planes. + * Local card axes in the (layer) mother frame: x = stave width, y = stave axis + * (radial at the tip), z = beam. The card sits downstream of the stave (away + * from the IP), just past the disk envelope, in front of the connection disk. + */ +void FT3Module::addEndOfStaveCard( + TGeoVolume* motherVolume, const std::string& name, int direction, + unsigned volume_count, double x_mid, double y_tip, double z_sensor, + bool isML, double cuThickness) +{ + const Constants::EosCardParams& c = Constants::getEosCardParams(isML); + if (cuThickness * c.nCopperLayers >= c.thickness) { + LOG(fatal) << "FT3 disk EoS card: Cu " << cuThickness << " cm x " << c.nCopperLayers + << " planes does not fit in the " << c.thickness << " cm card"; + } + + // FR4 board (x = width, y = thickness, z = length) + TGeoVolume* cardVol = gGeoManager->MakeBox(name.c_str(), getMedium(Materials::MaterialID::FR4), + c.width / 2, c.thickness / 2, c.length / 2); + cardVol->SetLineColor(kGreen + 3); + + // copper planes spread over the thickness (y), outermost two flush with the faces + TGeoVolume* planeVol = gGeoManager->MakeBox((name + "_Cu").c_str(), getMedium(Materials::MaterialID::Copper), + c.width / 2, cuThickness / 2, c.length / 2); + planeVol->SetLineColor(kOrange + 7); + const double span = c.thickness - cuThickness; + for (int i = 0; i < c.nCopperLayers; ++i) { + const double y = (c.nCopperLayers > 1) ? -span / 2 + i * span / (c.nCopperLayers - 1) : 0.; + cardVol->AddNode(planeVol, i, new TGeoTranslation(0, y, 0)); + } + + // z: IP-facing edge starts just after the sensors (z_sensor), body extends + // downstream (away from the IP), toward the connection disk. + // y: sit just radially OUTSIDE the stave outer tip so the long card runs past + // the carbon stave along z without overlapping it. + const double s = (direction == 1) ? 1.0 : -1.0; // downstream sign + const double zCard = z_sensor + s * (c.zGap + c.length / 2); + const double yClear = 0.05; // radial clearance to the stave tip + const double yCard = y_tip + (y_tip >= 0 ? 1.0 : -1.0) * (c.thickness / 2 + yClear); + motherVolume->AddNode(cardVol, volume_count, new TGeoTranslation(x_mid, yCard, zCard)); +} + /* * Create the carbon shell of one stave, staggered in z, plus the mirrored one * when the stave is built as two pieces. @@ -509,6 +553,47 @@ void FT3Module::add_stave_volumes( staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward); } + + // End-of-stave card at the outer-radius tip of each stave PIECE, placed + // downstream of the stave in front of the connection disk. A whole stave gets + // one card (the +y "top" tip). A split stave is built as two pieces (main + + // mirror), each reaching the outer radius at opposite ends, so each piece gets + // its own card at its outer tip (+y for the main, -y for the mirror). + // Shared by the exact and greedy paths; the disk layer envelope is extended + // downstream in FT3Layer::createLayer to contain the cards. + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); + if (ft3Params.addDiskEosCards) { + const bool isML = staveConfig.isML; + const double cuT = isML ? ft3Params.ft3EosCardCuThicknessML : ft3Params.ft3EosCardCuThicknessOT; + // Actual outer-radius tip of the stave: greedy clamps the stave to + // absAllowedYRange.second, exact leaves it unbounded (DBL_MAX), so take the + // smaller of the geometric end and the radial clamp. + const double yTip = std::min(y_midpoint + staveConfig.y_lengths[i_stave] / 2, + absAllowedYRange.second); + // Per-stave sensor silicon z in the layer frame, reconstructed from the + // carbon-face offset exactly as the sensor-placement loop does, so the card + // starts right after the sensors. + const double z_offset_to_silicon = z_offset_to_carbon_face + + Constants::epoxyThickness + Constants::kaptonThickness + + Constants::copperThickness + Constants::epoxyThickness + + Constants::siliconThickness / 2; + const double zOffMult = (direction == 1) ? -1.0 : 1.0; + double zStaveShiftSensors = 0.0; + if (!staveConfig.staveOnFront[i_stave]) { + zStaveShiftSensors = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing) + : -Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + } + const double zSensor = z_offset_to_silicon * zOffMult + zStaveShiftSensors; + const std::string cardBase = "FT3_EOSdiskCard_" + std::to_string(direction) + "_" + + std::to_string(layerNumber) + "_" + std::to_string(i_stave); + addEndOfStaveCard(motherVolume, cardBase + "_pos", direction, (*staveVolumeCount)++, + staveConfig.x_midpoints[i_stave], +yTip, zSensor, isML, cuT); + // split stave: the mirror piece reaches the outer radius at -y, give it a card too + if (mirrorStaveAroundX) { + addEndOfStaveCard(motherVolume, cardBase + "_neg", direction, (*staveVolumeCount)++, + staveConfig.x_midpoints[i_stave], -yTip, zSensor, isML, cuT); + } + } } /*