From 324574821d5ccefdea0d4d80b438323d4d2eeace Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 26 Mar 2026 13:01:32 +0100 Subject: [PATCH 01/30] Add separate file for constants in FT3 module creation --- .../FT3Simulation/FT3ModuleConstants.h | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h new file mode 100644 index 0000000000000..1c6a38dbd29ed --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -0,0 +1,119 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file FT3ModuleConstants.h +/// \brief Definition of various constants for tiling the modules of sensors + +#ifndef FT3MODULECONSTANTS_H +#define FT3MODULECONSTANTS_H + +#include +#include +#include + +namespace FT3ModuleConstants +{ + /* CURRENT STATUS: + * 25x32mm sensors, 2mm inactive on one side + * Most granular layout is 2x1 sensors, where the one on the right has the inactive region + * on the right, and the one on the left has the inactive region on the left. + * When stacking 2x1 modules, there is a 0.2mm gap between them. By default, we assume this + * gap to be ABOVE the most recently placed module. + * + * |<- 25mm ->|<- 25mm ->| + * _______________________ + * ----------------------- 0.2mm gap above + * | | | | | + * | | | | | + * | | | | | + * | | | | | 32mm sensor height + * | | | | | + * | | | | | + * ------------------------ + * + */ + // First set all layout constants for the rest of the function + const double single_sensor_width = 2.5; + const double single_sensor_height = 3.2; + const double inactive_width = 0.2; + const double sensor2x1_gap = 0.02; + + const double active_width = single_sensor_width - inactive_width; + const double active_height = single_sensor_height; + + const double sensor2x1_width = 2 * single_sensor_width; + const double sensor2x1_active_width = 2 * active_width; + const double sensor2x1_height = single_sensor_height; + + const double carbonFiberThickness = 0.01; + const double foamSpacingThickness = 1.0; + + /* + * Constants for staves are written for both positive + * and negative x even though they are just mirrored now, + * because there might be design changes in the future + * that require a non-mirrored layout, making it easier to + * change here if so required, even though it looks uglier now. + */ + // First define midpoints of staves that would overlap with inner disc + // EXCEPTION: Assumed mirrored around x-axis + // map from Stave ID (1-indexed from other documents) to midpoint + const std::map staveID_to_y_midpoint = { + {-2, 39.0}, + {-1, 41.4}, + {1, 41.4}, + {2, 39.0} + }; + // lengths of staves, their midpoint, and their face + const std::vector y_lengths = { + 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, + 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, + 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, + 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8 + }; + const std::vector x_midpoints = { + -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R + 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R + }; + // which side of the disc do we place the stave? + // accessed via stave index, NOT stave ID + const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R + }; + + // small helper function to get 1-indexed stave ID, counting from the middle outwards, + // with negative IDs on the left and positive IDs on the right + inline const int staveIdxToID(int staveIdx) { + unsigned nStavesOneSide = staveOnFront.size() / 2; + bool isRight = staveIdx >= nStavesOneSide; + return staveIdx - nStavesOneSide + isRight; + } + + const unsigned kSensorsPerStack = 1; + + // material properties + const double siliconThickness = 0.01; + const double copperThickness = 0.006; + const double kaptonThickness = 0.03; + const double epoxyThickness = 0.0012; + + const int SiColor = kGreen; + const int SiInactiveColor = kRed; + const int glueColor = kBlue; + const int CuColor = kBlack; + const int kaptonColor = kYellow; +} + +#endif // FT3MODULECONSTANTS_H \ No newline at end of file From 59ebef3c0b1a2fa849867035cf4b1326523ff2c3 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 26 Mar 2026 13:03:21 +0100 Subject: [PATCH 02/30] Create modular design in similar fashion as before, but with slightly different module placement. Rewrite structure to be more granular and extendable. The old code remains for backward compatibility for now --- .../include/FT3Simulation/FT3Module.h | 56 ++- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 363 ++++++++++++++++++ 2 files changed, 417 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 15ac6be995646..80b02d6f0569e 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -17,6 +17,15 @@ #include #include +#include + +#include "FT3Simulation/FT3ModuleConstants.h" + +// define types for y positions, second element is the stack height +using PositionType = std::pair; +using PositionTypes = std::vector; +using PosNegPositionTypes = std::pair; +namespace Constants = FT3ModuleConstants; class FT3Module { @@ -36,10 +45,53 @@ class FT3Module const char* mDetName; - static void createModule(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume); + static void createModule( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, const std::string& face, + const std::string& layout_type, TGeoVolume* motherVolume); + + void createModule_scopingV3( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, + const std::string& layout_type, TGeoVolume* motherVolume); private: - static void create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume); + static void create_layout( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, const std::string& face, + const std::string& layout_type, TGeoVolume* motherVolume); + + void create_layout_scopingV3( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, + const std::string& layout_type, TGeoVolume* motherVolume); + + // Helper functions + void fill_stave(PosNegPositionTypes& y_positions, double Rout, + double Rin, double x_left, + unsigned kSensorStack, double tolerance, + std::pair y_start); + void addDetectorVolume( + TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* sensor_count, + double x_mid, double y_mid, double z_mid, + double x_half_length, double y_half_length, double z_half_length); + + void add2x1GlueVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + std::string side_str, double x_mid, double y_mid, double z_mid, + std::string element_glued_to); + + void add2x1CopperVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + std::string side_str, double x_mid, double y_mid, double z_mid); + + void add2x1KaptonVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + std::string side_str, double x_mid, double y_mid, double z_mid); + + void addSingleSensorVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + double active_x_mid, double y_mid, double z_mid, std::string side_str, bool isLeft); }; #endif // FT3MODULE_H diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 4ed330c35ae59..63d05e99d861b 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -24,6 +24,7 @@ #include #include #include +#include TGeoMaterial* FT3Module::siliconMat = nullptr; TGeoMedium* FT3Module::siliconMed = nullptr; @@ -82,6 +83,358 @@ double calculate_y_circle(double x, double radius) return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0; } +/* + * This function is a helper function which will pad out the stave with sensors + * until there is no more space available. + * + * Arguments: + * y_positions: a pair of vectors, where each vector contains pairs of + * y position and stack height for the positive and negative y positions respectively. + * This argument will be appended with the new sensor positions and stack heights. + * Rout: the outer radius of the layer + * Rin: the inner radius of the layer + * x_left: the x position of the left edge of the sensor to be placed + * kSensorStack: the number of sensors to be stacked on top of each other + * tolerance: the tolerance to be subtracted from the maximum y position to avoid + * placing sensors too close to the edge. If this is negative, it effectively + * means that you can place sensors beyond the nominal disc edge + * y_start: the y positions to start placing sensors, + * for positive and negative y respectively + */ +void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, + double Rin, double x_left, + unsigned kSensorStack, double tolerance, + std::pair y_start={0, 0}) +{ + // start with upper half of the stave, then mirror to the bottom half + double x_right = x_left + Constants::sensor2x1_width; + double y_top = y_start.first; + // either start at given start position, or at the top of the last placed sensors + if (!y_positions.first.empty()) { + y_top = y_positions.first.back().first + + Constants::sensor2x1_height * y_positions.first.back().second + + Constants::sensor2x1_gap; + } + // add the height of kSensorStack sensors + the gaps in between them + double sensorTileHeight = Constants::sensor2x1_height * kSensorStack + + Constants::sensor2x1_gap * (kSensorStack - 1); + + double max_y_abs; + // y_max(x_left) > y_max(x_right) means that the top of the sensor + // will hit the outer radius at x_right first + if (x_left > -Constants::single_sensor_width) { + // tolerance already in maximum y position + max_y_abs = calculate_y_circle(x_right, Rout) - tolerance; + } else { + max_y_abs = calculate_y_circle(x_left, Rout) - tolerance; + } + unsigned n_sensors_placed = y_positions.first.size() + y_positions.second.size(); + LOG(info) << "\tFT3Module: Filling stave at x = " << (x_left + Constants::sensor2x1_width / 2) + << " with sensors of height " << sensorTileHeight + << ". Starting positive y position: " << y_top + << ", maximum positive y position: " << max_y_abs + << ", with initially " << n_sensors_placed << " sensors already placed."; + + while ( (y_top + sensorTileHeight) <= max_y_abs ) { + y_positions.first.emplace_back(y_top, kSensorStack); + LOG(info) << "\t\t\tFT3Module: Placed sensor at y = " << y_top; + y_top += sensorTileHeight + Constants::sensor2x1_gap; + } + + // now we do the same for the negative y positions + // they do not have to be exactly mirrored, hence done separately + double y_bottom = y_start.second; + if (!y_positions.second.empty()) { + // subtract instead to move further down + y_bottom = y_positions.second.back().first + - Constants::sensor2x1_height * y_positions.second.back().second + - Constants::sensor2x1_gap; + } + + LOG(info) << "\tFT3Module: Starting negative y position: " << y_bottom + << ", minimum negative y position: " << -max_y_abs; + while ( (y_bottom - sensorTileHeight) >= -max_y_abs ) { + y_positions.second.emplace_back(y_bottom, kSensorStack); + LOG(info) << "\t\t\tFT3Module: Placed sensor at y = " << y_bottom; + y_bottom -= (sensorTileHeight + Constants::sensor2x1_gap); + } + unsigned sensors_placed_after = y_positions.first.size() + y_positions.second.size(); + LOG(info) << "\tFT3Module: Done filling stave. Now have " + << sensors_placed_after << " sensors in total."; +} + +/* + * Generic helper function that adds a box at the given position with + * the given dimensions to the given mother volume, with the given color and name. + */ + +void FT3Module::addDetectorVolume( + TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* sensor_count, + double x_mid, double y_mid, double z_mid, + double x_half_length, double y_half_length, double z_half_length) +{ + TGeoManager* geoManager = gGeoManager; + TGeoVolume* volume = geoManager->MakeBox(volumeName.c_str(), siliconMed, x_half_length, + y_half_length, z_half_length); + volume->SetLineColor(color); + volume->SetFillColorAlpha(color, 0.4); + motherVolume->AddNode( + volume, + 1, + new TGeoTranslation( // midpoint of box to add + x_mid, + y_mid, + z_mid + ) // TGeoTranslation + ); // addNode +} + +/* + * This function adds a glue volume between two element layers, + * immediately for a whole 2x1 layout, under both the active and inactive region. + */ +void FT3Module::add2x1GlueVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + std::string side_str, double x_mid, double y_mid, double z_mid, + std::string element_glued_to) +{ + std::string glue_name = "FT3glue_" + element_glued_to + "_" + side_str + "_" + + std::to_string(layerNumber) + "_" + std::to_string(direction) + + "_" + std::to_string(*sensor_count); + addDetectorVolume( + motherVolume, glue_name, Constants::glueColor, sensor_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2 + ); +} + +/* + * This function adds a copper volume onto which the silicon sensor is glued. + * As with the glue, this is a whole 2x1 layout volume. + */ +void FT3Module::add2x1CopperVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + std::string side_str, double x_mid, double y_mid, double z_mid) +{ + std::string copper_name = "FT3Copper_" + side_str + "_" + std::to_string(layerNumber) + "_" + + std::to_string(direction) + "_" + std::to_string(*sensor_count); + addDetectorVolume( + motherVolume, copper_name, Constants::CuColor, sensor_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2 + ); +} + +/* + * This function adds a kapton volume behind the copper, which represents the ??? + * As with copper and glue, this is a whole 2x1 layout volume. + */ +void FT3Module::add2x1KaptonVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + std::string side_str, double x_mid, double y_mid, double z_mid) +{ + std::string kapton_name = "FT3Kapton_" + side_str + "_" + std::to_string(layerNumber) + "_" + + std::to_string(direction) + "_" + std::to_string(*sensor_count); + addDetectorVolume( + motherVolume, kapton_name, Constants::kaptonColor, sensor_count, + x_mid, y_mid, z_mid, + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2 + ); +} + +// TODO FOR TOMORROW 25/03: swap front and back for backward eta region, so that front faces the interaction region. + +/* + * This function adds a single sensor (currently 2.5x3.2mm) to the given mother volume + * at the given (x,y,z) position of the module. + * + * Because the sensor has an inactive region of 0.2mm on one side, we also add a + * separate volume for the inactive region, which will be either on the left or + * or right dependent on the if the sensor is on the left or right in a 2x1 layout. + * See FT3Module.h for more details on the layout. + * + * Arguments: + * motherVolume: the volume to which the sensor volume will be added + * layerNumber: the layer number of the sensor, used for naming + * direction: the direction of the sensor (forward or backward eta), used for naming + * x_mid: the x position of the center of the sensor volume + * y_mid: the y position of the center of the sensor volume + * z_mid: the z position of the center of the sensor volume + * side_str: string indicating whether the sensor is on the front or back + * isLeft: whether the sensor is on the left or right in the 2x1 layout + */ +void FT3Module::addSingleSensorVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + double active_x_mid, double y_mid, double z_mid, std::string side_str, bool isLeft) +{ + TGeoVolume* sensor; + TGeoManager* geoManager = gGeoManager; + // ACTIVE AREA + std::string sensor_name = "FT3Sensor_" + side_str + "_" + std::to_string(layerNumber) + "_" + + std::to_string(direction) + "_" + std::to_string(*sensor_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + sensor->SetLineColor(Constants::SiColor); + sensor->SetFillColorAlpha(Constants::SiColor, 0.4); + motherVolume->AddNode( + sensor, + *sensor_count++, + new TGeoTranslation( // midpoint of box to add + active_x_mid, + y_mid, + z_mid + ) // TGeoTranslation + ); // addNode + // INACTIVE STRIP ON LEFT OR RIGHT + double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2) + : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); + std::string sensor_inactive_left_name = + "FT3Sensor_InactiveLeft_" + side_str + "_" + std::to_string(layerNumber) + "_" + + std::to_string(direction) + "_" + std::to_string(*sensor_count); + sensor = geoManager->MakeBox(sensor_inactive_left_name.c_str(), siliconMed, Constants::inactive_width / 2, + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + sensor->SetLineColor(Constants::SiInactiveColor); + sensor->SetFillColorAlpha(Constants::SiInactiveColor, 0.4); + motherVolume->AddNode( + sensor, + *sensor_count++, + new TGeoTranslation( // midpoint of box to add + inactive_x_mid, + y_mid, + z_mid + ) // TGeoTranslation + ); // addNode +} + +void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int direction, + double Rin, double Rout, double overlap, + const std::string& layout_type, TGeoVolume* motherVolume) +{ + LOG(info) << "FT3Module: create_layout_scopingV3 - Layer " + << layerNumber << ", Direction " << direction; + + FT3Module::initialize_materials(); + + // initialise all y_positions, vector over all staves + std::vector y_positionsPosNeg; + // Fill all staves + for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { + y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); + + double y_midpoint = 0.; + const int staveID = Constants::staveIdxToID(i_stave); + auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); + if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave + y_midpoint = y_midpoint_it->second; // avoid double map lookup + double y_start_pos = y_midpoint - Constants::y_lengths[i_stave] / 2; + // currently we have the same positions on positive and negative x + // place the first sensor tile, assume it fits + y_positionsPosNeg.back().first.emplace_back(y_start_pos, Constants::kSensorsPerStack); // positive + y_positionsPosNeg.back().second.emplace_back(-y_start_pos, Constants::kSensorsPerStack); // negative + } else { + // midpoint is zero, i.e., the start point for x>0 & y>0 is y=0 + // while y_start for x>0, y<0 is -Constants::sensor2x1_gap + y_positionsPosNeg.back().first.emplace_back(0, Constants::kSensorsPerStack); // positive + y_positionsPosNeg.back().second.emplace_back(-Constants::sensor2x1_gap, Constants::kSensorsPerStack); // negative + } + double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; + // fill_stave(y_positionsPosNeg, Rout, Rin, x_left, Constants::kSensorsPerStack, -3); + LOG(info) << "FT3Module: Filling Stave " << staveID << " (x = " << Constants::x_midpoints[i_stave] + << ") with sensors. Starting y positions: " + << y_positionsPosNeg.back().first.front().first << " (positive), " + << y_positionsPosNeg.back().second.front().first << " (negative)."; + fill_stave(y_positionsPosNeg.back(), Rout, Rin, x_left, 1, -1); // easiest: just do with 2x1 for now + } + + unsigned sensor_count = 0; + for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { + double x_mid = Constants::x_midpoints[i_stave]; + LOG(info) << "FT3Module: Adding sensor volumes for Stave " << Constants::staveIdxToID(i_stave) + << " (x = " << x_mid << ") with " << y_positionsPosNeg[i_stave].first.size() << " positive and " + << y_positionsPosNeg[i_stave].second.size() << " negative sensor positions."; + for (unsigned i_y_pos = 0; i_y_pos < y_positionsPosNeg[i_stave].first.size(); i_y_pos++) { + for (unsigned i_y_sign = 0; i_y_sign < 2; i_y_sign++) { + // TODO: Make this loop over all sensors in a stack, don't just assume one sensor per stack + TGeoVolume* sensor; + // place sensors at positive and negative y + const auto& positions = (i_y_sign == 0) ? y_positionsPosNeg[i_stave].first + : y_positionsPosNeg[i_stave].second; + double y_mid = positions[i_y_pos].first + Constants::sensor2x1_height / 2; + + // get which side we are on + bool isFront = Constants::staveOnFront[i_stave]; + + /* + * we build the volume from the outside in, starting with the silicon, + * then glue & materials towards the stave. Depending on whether it's front or back, + * the distance from the center will be mirrored so that we get the following: + * + * Front (ordered in z, assuming the forward direction is to the right): + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | STAVE | SUPPORT STRUCTURE | + * + * Back (ordered in z, assuming the forward direction is to the right): + * | SUPPORT STRUCTURE | STAVE | GLUE | KAPTON | COPPER | GLUE | SILICON SENSOR | + * + * Note that we do not place stave and support structure material here, that is + * assumed to have been placed by the Layer creation. + */ + double z_offset_centre_to_stave = Constants::foamSpacingThickness / 2.0 + Constants::carbonFiberThickness; + double z_offset_stave_to_silicon = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness / 2; + double z_offset_stave_to_glue_Si = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness / 2; + double z_offset_stave_to_copper = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness / 2; + double z_offset_stave_to_kapton = Constants::epoxyThickness + Constants::kaptonThickness / 2; + double z_offset_stave_to_glue_Cu = Constants::epoxyThickness / 2; + + // for the front, we have to subtract the z offsets since we are going in + // negative z direction, while it's opposite for the back + int z_offset_multiplier = isFront ? -1 : 1; + std::string side_str = isFront ? "front" : "back"; + // ------------ (1) Silicon sensor ------------ + // left single sensor of the 2x1 + double z_mid = (z_offset_centre_to_stave + z_offset_stave_to_silicon) * z_offset_multiplier; + addSingleSensorVolume( + motherVolume, layerNumber, direction, &sensor_count, + x_mid - Constants::active_width / 2, y_mid, z_mid, side_str, true + ); + // right single sensor of the 2x1 + addSingleSensorVolume( + motherVolume, layerNumber, direction, &sensor_count, + x_mid + Constants::active_width / 2, y_mid, z_mid, side_str, false + ); + + // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Si) * z_offset_multiplier; + add2x1GlueVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid, "SiCu" + ); + // ------------ (3) Copper layer (FPC) ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_copper) * z_offset_multiplier; + add2x1CopperVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid + ); + // ------------ (4) Kapton layer (FPC) ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_kapton) * z_offset_multiplier; + add2x1KaptonVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid + ); + // ------------ (5) Epoxy glue layer between stave and FPC copper ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Cu) * z_offset_multiplier; + add2x1GlueVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid, "StaveKapton" + ); + } // for i_y_sign (writing of positive or negative y positions) + } // i_y_pos + } // i_stave + + +} + void FT3Module::create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume) { @@ -740,3 +1093,13 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R create_layout(mZ, layerNumber, direction, Rin, Rout, overlap, face, layout_type, motherVolume); LOG(debug) << "FT3Module: done createModule"; } + +void FT3Module::createModule_scopingV3(double mZ, int layerNumber, int direction, + double Rin, double Rout, double overlap, + const std::string& layout_type, + TGeoVolume* motherVolume) { + LOG(debug) << "FT3Module: createModule_scopingV3 - Layer " << layerNumber + << ", Direction " << direction; + create_layout_scopingV3(mZ, layerNumber, direction, Rin, Rout, overlap, layout_type, motherVolume); + LOG(debug) << "FT3Module: done createModule_scopingV3"; +} From e0c38f56419754dac568af0c3175852a6bff5869 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 26 Mar 2026 14:48:32 +0100 Subject: [PATCH 03/30] Give new geometry creation an FT3Layout enum and change Layer&Module accordingly. Also simplify create_layout_scopingV3 function since the layout_type isn't needed. --- .../ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h | 4 +++- .../simulation/include/FT3Simulation/FT3Module.h | 8 +++----- .../include/FT3Simulation/FT3ModuleConstants.h | 2 +- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 14 ++++++++++---- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 5 ++--- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 67bf42458a88a..0d4ba65c9ed60 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -24,10 +24,12 @@ enum eFT3Layout { kCylindrical = 0, kTrapezoidal, kSegmented, + kSegmentedMarch26, + kSegmentedStave, }; struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - eFT3Layout layoutFT3 = kSegmented; + eFT3Layout layoutFT3 = kSegmentedMarch26; int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 80b02d6f0569e..4d473c4bd4dfc 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -25,7 +25,7 @@ using PositionType = std::pair; using PositionTypes = std::vector; using PosNegPositionTypes = std::pair; -namespace Constants = FT3ModuleConstants; +namespace Constants = o2::ft3::ModuleConstants; class FT3Module { @@ -52,8 +52,7 @@ class FT3Module void createModule_scopingV3( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, - const std::string& layout_type, TGeoVolume* motherVolume); + double Rout, double overlap, TGeoVolume* motherVolume); private: static void create_layout( @@ -63,8 +62,7 @@ class FT3Module void create_layout_scopingV3( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, - const std::string& layout_type, TGeoVolume* motherVolume); + double Rout, double overlap, TGeoVolume* motherVolume); // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rout, diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index 1c6a38dbd29ed..d4608bb6900ce 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -19,7 +19,7 @@ #include #include -namespace FT3ModuleConstants +namespace o2::ft3::ModuleConstants { /* CURRENT STATUS: * 25x32mm sensors, 2mm inactive on one side diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 5be3c7abc30a3..400273c4a8dd1 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -381,7 +381,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); - } else if (ft3Params.layoutFT3 == kSegmented) { + } else if (ft3Params.layoutFT3 == kSegmented || ft3Params.layoutFT3 == kSegmentedMarch26) { FT3Module module; // layer structure @@ -398,9 +398,15 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) createSeparationLayer(layerVol, separationLayerName); // create disk faces - module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); - module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); - + if (ft3Params.layoutFT3 == kSegmented) { + module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); + module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); + } else if (ft3Params.layoutFT3 == kSegmentedMarch26) { + module.createModule_scopingV3(0, mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, 0., layerVol); + module.createModule_scopingV3(0, mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, 0., layerVol); + } // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ, FwdDiskRotation); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 63d05e99d861b..e320c01a3f000 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -308,7 +308,7 @@ void FT3Module::addSingleSensorVolume( void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, - const std::string& layout_type, TGeoVolume* motherVolume) + TGeoVolume* motherVolume) { LOG(info) << "FT3Module: create_layout_scopingV3 - Layer " << layerNumber << ", Direction " << direction; @@ -1096,10 +1096,9 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R void FT3Module::createModule_scopingV3(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, - const std::string& layout_type, TGeoVolume* motherVolume) { LOG(debug) << "FT3Module: createModule_scopingV3 - Layer " << layerNumber << ", Direction " << direction; - create_layout_scopingV3(mZ, layerNumber, direction, Rin, Rout, overlap, layout_type, motherVolume); + create_layout_scopingV3(mZ, layerNumber, direction, Rin, Rout, overlap, motherVolume); LOG(debug) << "FT3Module: done createModule_scopingV3"; } From 037580ef99fd6c8121719aa7e9c78e8af546d587 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 26 Mar 2026 21:27:33 +0100 Subject: [PATCH 04/30] Add functionality for the user to enforce a strict cut on module placement to remain within nominal radii. Also remove initial placement of sensor stack before calling fill_stave, moving to using y_start instead. --- .../FT3/base/include/FT3Base/FT3BaseParam.h | 3 ++ .../include/FT3Simulation/FT3Module.h | 3 +- .../FT3Simulation/FT3ModuleConstants.h | 6 ++- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 50 ++++++++++++------- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 0d4ba65c9ed60..84c08e615d33a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -40,6 +40,9 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { Float_t etaOut = 1.5; Float_t Layerx2X0 = 0.01; + // override values from FT3ModuleConstants + bool cutStavesOnNominalRadius = false; + O2ParamDef(FT3BaseParam, "FT3Base"); }; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 4d473c4bd4dfc..eed684d7091ff 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -66,8 +66,7 @@ class FT3Module // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rout, - double Rin, double x_left, - unsigned kSensorStack, double tolerance, + double x_left, unsigned kSensorStack, double tolerance, std::pair y_start); void addDetectorVolume( TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* sensor_count, diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index d4608bb6900ce..471d53a12904a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -52,6 +52,9 @@ namespace o2::ft3::ModuleConstants const double sensor2x1_width = 2 * single_sensor_width; const double sensor2x1_active_width = 2 * active_width; const double sensor2x1_height = single_sensor_height; + const unsigned kSensorsPerStack = 1; + const double sensor_stack_height = kSensorsPerStack * sensor2x1_height + + (kSensorsPerStack - 1) * sensor2x1_gap; const double carbonFiberThickness = 0.01; const double foamSpacingThickness = 1.0; @@ -66,6 +69,7 @@ namespace o2::ft3::ModuleConstants // First define midpoints of staves that would overlap with inner disc // EXCEPTION: Assumed mirrored around x-axis // map from Stave ID (1-indexed from other documents) to midpoint + // Do NOT add any zero midpoints, this is taken off separately const std::map staveID_to_y_midpoint = { {-2, 39.0}, {-1, 41.4}, @@ -101,8 +105,6 @@ namespace o2::ft3::ModuleConstants return staveIdx - nStavesOneSide + isRight; } - const unsigned kSensorsPerStack = 1; - // material properties const double siliconThickness = 0.01; const double copperThickness = 0.006; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index e320c01a3f000..46c796236d24d 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -13,6 +13,7 @@ /// \brief Implementation of the FT3Module class #include "FT3Simulation/FT3Module.h" +#include "FT3Base/FT3BaseParam.h" #include #include #include @@ -102,8 +103,7 @@ double calculate_y_circle(double x, double radius) * for positive and negative y respectively */ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, - double Rin, double x_left, - unsigned kSensorStack, double tolerance, + double x_left, unsigned kSensorStack, double tolerance, std::pair y_start={0, 0}) { // start with upper half of the stave, then mirror to the bottom half @@ -314,6 +314,7 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio << layerNumber << ", Direction " << direction; FT3Module::initialize_materials(); + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); // initialise all y_positions, vector over all staves std::vector y_positionsPosNeg; @@ -322,28 +323,41 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); double y_midpoint = 0.; + // default positive and negative starting points has a gap around x-axis + std::pair y_start{0., Constants::sensor2x1_gap}; const int staveID = Constants::staveIdxToID(i_stave); auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); - if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave + if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { + // there is a defined midpoint for this stave, use this for starting points y_midpoint = y_midpoint_it->second; // avoid double map lookup double y_start_pos = y_midpoint - Constants::y_lengths[i_stave] / 2; - // currently we have the same positions on positive and negative x - // place the first sensor tile, assume it fits - y_positionsPosNeg.back().first.emplace_back(y_start_pos, Constants::kSensorsPerStack); // positive - y_positionsPosNeg.back().second.emplace_back(-y_start_pos, Constants::kSensorsPerStack); // negative - } else { - // midpoint is zero, i.e., the start point for x>0 & y>0 is y=0 - // while y_start for x>0, y<0 is -Constants::sensor2x1_gap - y_positionsPosNeg.back().first.emplace_back(0, Constants::kSensorsPerStack); // positive - y_positionsPosNeg.back().second.emplace_back(-Constants::sensor2x1_gap, Constants::kSensorsPerStack); // negative } + double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; - // fill_stave(y_positionsPosNeg, Rout, Rin, x_left, Constants::kSensorsPerStack, -3); - LOG(info) << "FT3Module: Filling Stave " << staveID << " (x = " << Constants::x_midpoints[i_stave] - << ") with sensors. Starting y positions: " - << y_positionsPosNeg.back().first.front().first << " (positive), " - << y_positionsPosNeg.back().second.front().first << " (negative)."; - fill_stave(y_positionsPosNeg.back(), Rout, Rin, x_left, 1, -1); // easiest: just do with 2x1 for now + double x_right = x_left + Constants::sensor2x1_width; + double tolerance = -Constants::sensor_stack_height; // allow one sensor placement beyond + // cut staves on nominal inner radius if specified + if (ft3Params.cutStavesOnNominalRadius) { + double min_y_at_x; + if (x_left * x_right < 0) { + // stave crosses y-axis, so we start at y=Rin + min_y_at_x = Rin; + } else if (x_left > 0) { + // stave is on the right side, so minimum y is at x_left + min_y_at_x = calculate_y_circle(x_left, Rin); + } else { + // stave is on the left side, so minimum y is at x_right + min_y_at_x = calculate_y_circle(x_right, Rin); + } + y_start = {min_y_at_x, -min_y_at_x}; + tolerance = 0.; // no tolerance in case of cutting at nominal radius + } + // fill_stave(y_positionsPosNeg, Rout, x_left, Constants::kSensorsPerStack, -3); + LOG(info) << "FT3Module: Filling Stave " << staveID << " (x = " + << Constants::x_midpoints[i_stave] << ") with sensors. Starting y positions: " + << y_start.first << " (positive), " << y_start.second << " (negative)."; + fill_stave(y_positionsPosNeg.back(), Rout, x_left, Constants::kSensorsPerStack, + tolerance, y_start); } unsigned sensor_count = 0; From d76f58256a8cc6b14c7f0dda5de9c9ba19e6988d Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Fri, 27 Mar 2026 10:13:39 +0100 Subject: [PATCH 05/30] Make forward and backward directions mirrored, front and back faces swap based on direction --- .../Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 46c796236d24d..5232446725cec 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -242,8 +242,6 @@ void FT3Module::add2x1KaptonVolume( ); } -// TODO FOR TOMORROW 25/03: swap front and back for backward eta region, so that front faces the interaction region. - /* * This function adds a single sensor (currently 2.5x3.2mm) to the given mother volume * at the given (x,y,z) position of the module. @@ -375,9 +373,13 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio : y_positionsPosNeg[i_stave].second; double y_mid = positions[i_y_pos].first + Constants::sensor2x1_height / 2; - // get which side we are on - bool isFront = Constants::staveOnFront[i_stave]; - + // get which side we are on: if backward discs we mirror from front so it's the same + // layout from the frame of the particle, regardless which direction + bool isFront; + if (!direction) // direction = 0 is forward + isFront = Constants::staveOnFront[i_stave]; + else + isFront = !(Constants::staveOnFront[i_stave]); /* * we build the volume from the outside in, starting with the silicon, * then glue & materials towards the stave. Depending on whether it's front or back, From 298386017a461a36e2def77f17a39a1593819b59 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Fri, 27 Mar 2026 10:24:03 +0100 Subject: [PATCH 06/30] Add loop over all sensors in stack so all sensors are added to volume, not just the first in the stack --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 154 +++++++++--------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 5232446725cec..5e8d742167167 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -366,84 +366,88 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio << y_positionsPosNeg[i_stave].second.size() << " negative sensor positions."; for (unsigned i_y_pos = 0; i_y_pos < y_positionsPosNeg[i_stave].first.size(); i_y_pos++) { for (unsigned i_y_sign = 0; i_y_sign < 2; i_y_sign++) { - // TODO: Make this loop over all sensors in a stack, don't just assume one sensor per stack - TGeoVolume* sensor; // place sensors at positive and negative y const auto& positions = (i_y_sign == 0) ? y_positionsPosNeg[i_stave].first : y_positionsPosNeg[i_stave].second; - double y_mid = positions[i_y_pos].first + Constants::sensor2x1_height / 2; - - // get which side we are on: if backward discs we mirror from front so it's the same - // layout from the frame of the particle, regardless which direction - bool isFront; - if (!direction) // direction = 0 is forward - isFront = Constants::staveOnFront[i_stave]; - else - isFront = !(Constants::staveOnFront[i_stave]); - /* - * we build the volume from the outside in, starting with the silicon, - * then glue & materials towards the stave. Depending on whether it's front or back, - * the distance from the center will be mirrored so that we get the following: - * - * Front (ordered in z, assuming the forward direction is to the right): - * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | STAVE | SUPPORT STRUCTURE | - * - * Back (ordered in z, assuming the forward direction is to the right): - * | SUPPORT STRUCTURE | STAVE | GLUE | KAPTON | COPPER | GLUE | SILICON SENSOR | - * - * Note that we do not place stave and support structure material here, that is - * assumed to have been placed by the Layer creation. - */ - double z_offset_centre_to_stave = Constants::foamSpacingThickness / 2.0 + Constants::carbonFiberThickness; - double z_offset_stave_to_silicon = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness - + Constants::epoxyThickness + Constants::siliconThickness / 2; - double z_offset_stave_to_glue_Si = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness - + Constants::epoxyThickness / 2; - double z_offset_stave_to_copper = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness / 2; - double z_offset_stave_to_kapton = Constants::epoxyThickness + Constants::kaptonThickness / 2; - double z_offset_stave_to_glue_Cu = Constants::epoxyThickness / 2; - - // for the front, we have to subtract the z offsets since we are going in - // negative z direction, while it's opposite for the back - int z_offset_multiplier = isFront ? -1 : 1; - std::string side_str = isFront ? "front" : "back"; - // ------------ (1) Silicon sensor ------------ - // left single sensor of the 2x1 - double z_mid = (z_offset_centre_to_stave + z_offset_stave_to_silicon) * z_offset_multiplier; - addSingleSensorVolume( - motherVolume, layerNumber, direction, &sensor_count, - x_mid - Constants::active_width / 2, y_mid, z_mid, side_str, true - ); - // right single sensor of the 2x1 - addSingleSensorVolume( - motherVolume, layerNumber, direction, &sensor_count, - x_mid + Constants::active_width / 2, y_mid, z_mid, side_str, false - ); - - // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Si) * z_offset_multiplier; - add2x1GlueVolume( - motherVolume, layerNumber, direction, &sensor_count, - side_str, x_mid, y_mid, z_mid, "SiCu" - ); - // ------------ (3) Copper layer (FPC) ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_copper) * z_offset_multiplier; - add2x1CopperVolume( - motherVolume, layerNumber, direction, &sensor_count, - side_str, x_mid, y_mid, z_mid - ); - // ------------ (4) Kapton layer (FPC) ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_kapton) * z_offset_multiplier; - add2x1KaptonVolume( - motherVolume, layerNumber, direction, &sensor_count, - side_str, x_mid, y_mid, z_mid - ); - // ------------ (5) Epoxy glue layer between stave and FPC copper ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Cu) * z_offset_multiplier; - add2x1GlueVolume( - motherVolume, layerNumber, direction, &sensor_count, - side_str, x_mid, y_mid, z_mid, "StaveKapton" - ); + for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) { + TGeoVolume* sensor; + double y_mid = // y = y_start + (height + gap of one sensor) * sensor index + positions[i_y_pos].first + + (Constants::sensor2x1_height + Constants::sensor2x1_gap) * i_sens + + Constants::sensor2x1_height / 2; // and add half height to get the middle of the sensor + + // get which side we are on: if backward discs we mirror from front so it's the same + // layout from the frame of the particle, regardless which direction + bool isFront; + if (!direction) // direction = 0 is forward + isFront = Constants::staveOnFront[i_stave]; + else + isFront = !(Constants::staveOnFront[i_stave]); + /* + * we build the volume from the outside in, starting with the silicon, + * then glue & materials towards the stave. Depending on whether it's front or back, + * the distance from the center will be mirrored so that we get the following: + * + * Front (ordered in z, assuming the forward direction is to the right): + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | STAVE | SUPPORT STRUCTURE | + * + * Back (ordered in z, assuming the forward direction is to the right): + * | SUPPORT STRUCTURE | STAVE | GLUE | KAPTON | COPPER | GLUE | SILICON SENSOR | + * + * Note that we do not place stave and support structure material here, that is + * assumed to have been placed by the Layer creation. + */ + double z_offset_centre_to_stave = Constants::foamSpacingThickness / 2.0 + Constants::carbonFiberThickness; + double z_offset_stave_to_silicon = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness / 2; + double z_offset_stave_to_glue_Si = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness / 2; + double z_offset_stave_to_copper = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness / 2; + double z_offset_stave_to_kapton = Constants::epoxyThickness + Constants::kaptonThickness / 2; + double z_offset_stave_to_glue_Cu = Constants::epoxyThickness / 2; + + // for the front, we have to subtract the z offsets since we are going in + // negative z direction, while it's opposite for the back + int z_offset_multiplier = isFront ? -1 : 1; + std::string side_str = isFront ? "front" : "back"; + // ------------ (1) Silicon sensor ------------ + // left single sensor of the 2x1 + double z_mid = (z_offset_centre_to_stave + z_offset_stave_to_silicon) * z_offset_multiplier; + addSingleSensorVolume( + motherVolume, layerNumber, direction, &sensor_count, + x_mid - Constants::active_width / 2, y_mid, z_mid, side_str, true + ); + // right single sensor of the 2x1 + addSingleSensorVolume( + motherVolume, layerNumber, direction, &sensor_count, + x_mid + Constants::active_width / 2, y_mid, z_mid, side_str, false + ); + + // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Si) * z_offset_multiplier; + add2x1GlueVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid, "SiCu" + ); + // ------------ (3) Copper layer (FPC) ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_copper) * z_offset_multiplier; + add2x1CopperVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid + ); + // ------------ (4) Kapton layer (FPC) ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_kapton) * z_offset_multiplier; + add2x1KaptonVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid + ); + // ------------ (5) Epoxy glue layer between stave and FPC copper ------------ + z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Cu) * z_offset_multiplier; + add2x1GlueVolume( + motherVolume, layerNumber, direction, &sensor_count, + side_str, x_mid, y_mid, z_mid, "StaveKapton" + ); + } // sensors in stack } // for i_y_sign (writing of positive or negative y positions) } // i_y_pos } // i_stave From e4539fb9f7c7c8f03e0c88470f1e0e56720cb98d Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 10:52:01 +0200 Subject: [PATCH 07/30] Add constants for hollow stave based geometry --- .../FT3Simulation/FT3ModuleConstants.h | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index 471d53a12904a..091b9b755a7ec 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace o2::ft3::ModuleConstants { @@ -56,6 +57,7 @@ namespace o2::ft3::ModuleConstants const double sensor_stack_height = kSensorsPerStack * sensor2x1_height + (kSensorsPerStack - 1) * sensor2x1_gap; + // OLD VERSION: RECTANGULAR STAVES const double carbonFiberThickness = 0.01; const double foamSpacingThickness = 1.0; @@ -89,7 +91,10 @@ namespace o2::ft3::ModuleConstants 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R }; + const double x_midpoint_spacing = 4.5; // assume constant for now // which side of the disc do we place the stave? + // used for kSegmentedMarch26 for front/back face, and for + // kSegmentedStave for staggering staves in z (see z_offsetStave) // accessed via stave index, NOT stave ID const std::vector staveOnFront = { @@ -100,7 +105,7 @@ namespace o2::ft3::ModuleConstants // small helper function to get 1-indexed stave ID, counting from the middle outwards, // with negative IDs on the left and positive IDs on the right inline const int staveIdxToID(int staveIdx) { - unsigned nStavesOneSide = staveOnFront.size() / 2; + unsigned nStavesOneSide = y_lengths.size() / 2; bool isRight = staveIdx >= nStavesOneSide; return staveIdx - nStavesOneSide + isRight; } @@ -111,11 +116,32 @@ namespace o2::ft3::ModuleConstants const double kaptonThickness = 0.03; const double epoxyThickness = 0.0012; + const double effectiveCarbonThickness_Stave = 0.02; // foam + shell + const double staveOpeningAngle = 60 * TMath::DegToRad(); + const double sinTheta = TMath::Sin(staveOpeningAngle / 2); + const double alpha = TMath::Pi() / 2 - staveOpeningAngle / 2; // bottom angles + const double staveSensorGap = 0.1; // 2mm padding on each side when sensor is glued + const double staveTriangleHeight = (sensor2x1_width + 2 * staveSensorGap) / 2.0 + / tan(staveOpeningAngle / 2.0); + /* + * Now describe the offset of every other stave in z to avoid overlaps + * ______ ______ + * \ /______\ / | <-- z_offsetStave + * \ / \ / \ / + * \/ \ / \/ + * \/ + */ + // If midpoint spacing becomes non constant, this becomes a function + // TODO: add some tolerance to avoid overlaps? + const double z_offsetStave = + staveTriangleHeight * (2 - x_midpoint_spacing / ( sensor2x1_width / 2 + staveSensorGap ) ); + const int SiColor = kGreen; const int SiInactiveColor = kRed; const int glueColor = kBlue; - const int CuColor = kBlack; + const int CuColor = kOrange; const int kaptonColor = kYellow; + const int carbonColor = kBlack; } #endif // FT3MODULECONSTANTS_H \ No newline at end of file From c0b6b7079677e4a43d78d110b057084de9dd36e2 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 10:55:47 +0200 Subject: [PATCH 08/30] Add carbon fiber material, functionality to toggle between stave and old geometry. Also rename material counts to 'volume' instead of 'sensor'. EDIT: Change TGeo layer thickness back to original. EDIT2: Change Middle layer disc usage to newly added segmentation. --- .../include/FT3Simulation/FT3Module.h | 22 +++++++++++++------ .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 17 +++++++++----- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index eed684d7091ff..9d7f83bbeab47 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -42,6 +42,8 @@ class FT3Module static TGeoMedium* epoxyMed; static TGeoMaterial* AluminumMat; static TGeoMedium* AluminumMed; + static TGeoMaterial* carbonFiberMat; + static TGeoMedium* carbonFiberMed; const char* mDetName; @@ -52,7 +54,8 @@ class FT3Module void createModule_scopingV3( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, TGeoVolume* motherVolume); + double Rout, double overlap, TGeoVolume* motherVolume, + bool useStaves); private: static void create_layout( @@ -62,32 +65,37 @@ class FT3Module void create_layout_scopingV3( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, TGeoVolume* motherVolume); + double Rout, double overlap, TGeoVolume* motherVolume, + bool useStaves); // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rout, double x_left, unsigned kSensorStack, double tolerance, std::pair y_start); + void addStaveVolume( + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + double x_mid, double y_mid, double z_stave_shift_abs); void addDetectorVolume( - TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* sensor_count, + TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, double x_mid, double y_mid, double z_mid, double x_half_length, double y_half_length, double z_half_length); void add2x1GlueVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, std::string side_str, double x_mid, double y_mid, double z_mid, std::string element_glued_to); void add2x1CopperVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, std::string side_str, double x_mid, double y_mid, double z_mid); void add2x1KaptonVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, std::string side_str, double x_mid, double y_mid, double z_mid); void addSingleSensorVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, std::string side_str, bool isLeft); }; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 400273c4a8dd1..1ba0cf8143f39 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -234,7 +234,9 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(fatal) << "Invalid layer number " << mLayerNumber << " for FT3 layer."; } - LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3; + LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3 + << " Creating Layer " << mLayerNumber << " at z=" << mZ + << " with direction " << mDirection; // ### options for ML and OT disk layout if (ft3Params.layoutFT3 == kTrapezoidal /*|| (mIsMiddleLayer && ft3Params.layoutFT3 == kSegmented)*/) { @@ -381,7 +383,9 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); - } else if (ft3Params.layoutFT3 == kSegmented || ft3Params.layoutFT3 == kSegmentedMarch26) { + } else if (ft3Params.layoutFT3 == kSegmented || + ft3Params.layoutFT3 == kSegmentedMarch26 || + ft3Params.layoutFT3 == kSegmentedStave) { FT3Module module; // layer structure @@ -402,10 +406,11 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); } else if (ft3Params.layoutFT3 == kSegmentedMarch26) { - module.createModule_scopingV3(0, mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, 0., layerVol); - module.createModule_scopingV3(0, mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, 0., layerVol); + module.createModule_scopingV3(0., mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, 0., layerVol, false); + } else if (ft3Params.layoutFT3 == kSegmentedStave) { + module.createModule_scopingV3(0., mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, 0., layerVol, true); } // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); From e5fd95e91dd6d8409ddb79ac5169da0f2d0af6ec Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 10:56:12 +0200 Subject: [PATCH 09/30] Add full hollow stave geometry functionality --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 348 ++++++++++++++---- 1 file changed, 273 insertions(+), 75 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 5e8d742167167..d7bdde5bb20ed 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -18,7 +18,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -42,6 +44,9 @@ TGeoMedium* FT3Module::epoxyMed = nullptr; TGeoMaterial* FT3Module::AluminumMat = nullptr; TGeoMedium* FT3Module::AluminumMed = nullptr; +TGeoMaterial* FT3Module::carbonFiberMat = nullptr; +TGeoMedium* FT3Module::carbonFiberMed = nullptr; + void FT3Module::initialize_materials() { LOG(debug) << "FT3Module: initialize_materials"; @@ -64,6 +69,10 @@ void FT3Module::initialize_materials() kaptonMat = new TGeoMaterial("FT3_Kapton", 13.84, 6.88, 1.346); kaptonMed = new TGeoMedium("FT3_Kapton", 3, kaptonMat); + // TODO: Check with Rene the exact type of carbon fiber + carbonFiberMat = new TGeoMaterial("FT3_Carbon", 12.0107, 6, 1.8); + carbonFiberMed = new TGeoMedium("FT3_Carbon", 6, carbonFiberMat); + // Epoxy: C18 H19 O3 auto* itsEpoxy = new TGeoMixture("FT3_Epoxy", 3); itsEpoxy->AddElement(itsC, 18); @@ -163,13 +172,129 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, << sensors_placed_after << " sensors in total."; } +/* + * This function creates a carbon fibre volume for the stave, + * onto which the sensor and its support will be glued. + */ +void FT3Module::addStaveVolume( + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + double x_mid, double y_mid, double z_stave_shift_abs) +{ + // Set some constants for readability + double d = Constants::effectiveCarbonThickness_Stave; + double H = Constants::staveTriangleHeight; + LOG(info) << "\tFT3Module: Adding stave volume " << volumeName + << " with count " << *volume_count + << " at (x_mid,y_mid,z_stave_shift_abs) = (" << x_mid << ", " + << y_mid << ", " << z_stave_shift_abs << "), as well as length " + << staveLength << " and triangle height " << H; + /* + * Inner and outer vertices of the stave cross section triangle + * all vertices are at y_mid, we simply extend the triangle into y dir. + * We work in the local coordinate system of the stave, but still + * call the coordinates x and z for readability. + * + * 1. Get all local coordinates of the two triangle vertices + * 2. Extrude a volume from the subtracted triangle cross section area + * 3. Rotate the volume around the x-axis since it is by default in xy, + * and extruded in z. Rotate by -90 for xz -> xy, otherwise xz -> x(-y) + * 4. Translate the volume to the given position (arguments) + * + */ + double xv_inner[3], xv_outer[3], zv_inner[3], zv_outer[3]; + // calculate the coordinates of the triangle vertices + // Top/bottom vertex (apex) + xv_outer[0] = 0; + zv_outer[0] = (direction == 1) ? -H + : H;; + // right + xv_outer[1] = Constants::sensor2x1_width / 2 + Constants::staveSensorGap; + zv_outer[1] = 0; + // left + xv_outer[2] = -xv_outer[1]; + zv_outer[2] = 0; + + // now get inner vertices, shifted inwards by effective carbon thickness + xv_inner[0] = xv_outer[0]; + double z_shift_inner = d / Constants::sinTheta; + zv_inner[0] = (direction == 1) ? + zv_outer[0] + z_shift_inner + : zv_outer[0] - z_shift_inner; + // face vertices, first right + zv_inner[1] = (direction == 1) ? + zv_outer[1] - d + : zv_outer[1] + d; + double x_shift_abs = d / TMath::Tan(Constants::alpha / 2); + xv_inner[1] = xv_outer[1] - x_shift_abs; + // left + zv_inner[2] = zv_inner[1]; + xv_inner[2] = -xv_inner[1]; + + // create the extruded volumes from z=0 (later y=0 after rotation) to stave length + // and not from midpoint - staveLength/2 to midpoint + staveLength/2, + // translate after rotation + TGeoXtru* staveFull = new TGeoXtru(2); + staveFull->SetName(( volumeName + "_Xtru_outer").c_str()); + staveFull->DefinePolygon(3, xv_outer, zv_outer); + staveFull->DefineSection(0, 0); + staveFull->DefineSection(1, staveLength); + + TGeoXtru* staveInner = new TGeoXtru(2); + staveInner->SetName(( volumeName + "_Xtru_inner").c_str()); + staveInner->DefinePolygon(3, xv_inner, zv_inner); + staveInner->DefineSection(0, 0); + staveInner->DefineSection(1, staveLength); + + TGeoCompositeShape* staveShape = new TGeoCompositeShape( + (volumeName + "_shape").c_str(), + Form("%s - %s", staveFull->GetName(), staveInner->GetName()) + ); + TGeoVolume* staveVolume = new TGeoVolume( + (volumeName).c_str(), + staveShape, + carbonFiberMed + ); + if (*volume_count == 0 && direction == 1) { + LOG(info) << "Outer triangle vertices (x, z): " + << "(" << xv_outer[0] << ", " << zv_outer[0] << "), " + << "(" << xv_outer[1] << ", " << zv_outer[1] << "), " + << "(" << xv_outer[2] << ", " << zv_outer[2] << ")"; + LOG(info) << "Inner triangle vertices (x, z): " + << "(" << xv_inner[0] << ", " << zv_inner[0] << "), " + << "(" << xv_inner[1] << ", " << zv_inner[1] << "), " + << "(" << xv_inner[2] << ", " << zv_inner[2] << ")"; + } + TGeoRotation* rot = new TGeoRotation(); + rot->RotateX(-90); // lift from xy plane into xz plane + /* + * After rotations the face of the stave lies in the xy-plane, + * facing downwards for direction == 1 and upwards for direction == 0. + * We still need to shift it in z to get the right staggered layout. + * This means moving the staves that must be shifted in the opposite + * direction they are facing: up for direction 1, and down for direction 0. + */ + double z_shift = (direction == 1) ? z_stave_shift_abs : -z_stave_shift_abs; + TGeoCombiTrans* combiTrans = + new TGeoCombiTrans(x_mid, y_mid - staveLength / 2, z_shift, rot); + motherVolume->AddNode(staveVolume, + *volume_count, + combiTrans); + (*volume_count)++; + // print: + TGeoNode* node = motherVolume->GetNode(motherVolume->GetNdaughters() - 1); + const Double_t* translation = node->GetMatrix()->GetTranslation(); + LOG(info) << "\t\tStave node position: (" << translation[0] << ", " + << translation[1] << ", " << translation[2] << ")"; +} + /* * Generic helper function that adds a box at the given position with * the given dimensions to the given mother volume, with the given color and name. */ void FT3Module::addDetectorVolume( - TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* sensor_count, + TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, double x_mid, double y_mid, double z_mid, double x_half_length, double y_half_length, double z_half_length) { @@ -180,13 +305,14 @@ void FT3Module::addDetectorVolume( volume->SetFillColorAlpha(color, 0.4); motherVolume->AddNode( volume, - 1, + *volume_count, new TGeoTranslation( // midpoint of box to add x_mid, y_mid, z_mid ) // TGeoTranslation ); // addNode + (*volume_count)++; } /* @@ -194,15 +320,15 @@ void FT3Module::addDetectorVolume( * immediately for a whole 2x1 layout, under both the active and inactive region. */ void FT3Module::add2x1GlueVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, std::string side_str, double x_mid, double y_mid, double z_mid, std::string element_glued_to) { std::string glue_name = "FT3glue_" + element_glued_to + "_" + side_str + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction) - + "_" + std::to_string(*sensor_count); + + "_" + std::to_string(*volume_count); addDetectorVolume( - motherVolume, glue_name, Constants::glueColor, sensor_count, + motherVolume, glue_name, Constants::glueColor, volume_count, x_mid, y_mid, z_mid, Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2 ); @@ -213,13 +339,13 @@ void FT3Module::add2x1GlueVolume( * As with the glue, this is a whole 2x1 layout volume. */ void FT3Module::add2x1CopperVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, std::string side_str, double x_mid, double y_mid, double z_mid) { std::string copper_name = "FT3Copper_" + side_str + "_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*sensor_count); + + std::to_string(direction) + "_" + std::to_string(*volume_count); addDetectorVolume( - motherVolume, copper_name, Constants::CuColor, sensor_count, + motherVolume, copper_name, Constants::CuColor, volume_count, x_mid, y_mid, z_mid, Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2 ); @@ -230,13 +356,13 @@ void FT3Module::add2x1CopperVolume( * As with copper and glue, this is a whole 2x1 layout volume. */ void FT3Module::add2x1KaptonVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, std::string side_str, double x_mid, double y_mid, double z_mid) { std::string kapton_name = "FT3Kapton_" + side_str + "_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*sensor_count); + + std::to_string(direction) + "_" + std::to_string(*volume_count); addDetectorVolume( - motherVolume, kapton_name, Constants::kaptonColor, sensor_count, + motherVolume, kapton_name, Constants::kaptonColor, volume_count, x_mid, y_mid, z_mid, Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2 ); @@ -262,51 +388,53 @@ void FT3Module::add2x1KaptonVolume( * isLeft: whether the sensor is on the left or right in the 2x1 layout */ void FT3Module::addSingleSensorVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* sensor_count, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, std::string side_str, bool isLeft) { TGeoVolume* sensor; TGeoManager* geoManager = gGeoManager; // ACTIVE AREA std::string sensor_name = "FT3Sensor_" + side_str + "_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*sensor_count); + + std::to_string(direction) + "_" + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); sensor->SetLineColor(Constants::SiColor); sensor->SetFillColorAlpha(Constants::SiColor, 0.4); motherVolume->AddNode( sensor, - *sensor_count++, + *volume_count, new TGeoTranslation( // midpoint of box to add active_x_mid, y_mid, z_mid ) // TGeoTranslation ); // addNode + (*volume_count)++; // INACTIVE STRIP ON LEFT OR RIGHT double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2) : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); - std::string sensor_inactive_left_name = - "FT3Sensor_InactiveLeft_" + side_str + "_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*sensor_count); - sensor = geoManager->MakeBox(sensor_inactive_left_name.c_str(), siliconMed, Constants::inactive_width / 2, + std::string sensor_inactive_name = + "FT3Sensor_Inactive_" + side_str + "_" + std::to_string(layerNumber) + "_" + + std::to_string(direction) + "_" + std::to_string(*volume_count); + sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); sensor->SetLineColor(Constants::SiInactiveColor); sensor->SetFillColorAlpha(Constants::SiInactiveColor, 0.4); motherVolume->AddNode( sensor, - *sensor_count++, + *volume_count, new TGeoTranslation( // midpoint of box to add inactive_x_mid, y_mid, z_mid ) // TGeoTranslation ); // addNode + (*volume_count)++; } void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, - TGeoVolume* motherVolume) + TGeoVolume* motherVolume, bool useStaves) { LOG(info) << "FT3Module: create_layout_scopingV3 - Layer " << layerNumber << ", Direction " << direction; @@ -314,21 +442,90 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio FT3Module::initialize_materials(); auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); - // initialise all y_positions, vector over all staves + // First let's define some constants used throughout + /* + * we build the volume from the outside in, starting with the silicon, + * then glue & materials towards the stave/slab. Depending on whether it's front or back, + * the distance from the center will be mirrored so that we get the following: + * + * Front (ordered in z, since the forward direction is to the right): + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON SLAB | + * + * Back (ordered in z, since the forward direction is to the right): + * | CARBON SLAB | GLUE | KAPTON | COPPER | GLUE | SILICON SENSOR | + * + * Note that we do not place stave and support structure material here, that is + * assumed to have been placed by the Layer creation. + * + * kSegmentedStave: With triangular carbon staves we have the same order: + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | + * + * Thus, we can use same constant names for staves and slabs, + * as the material order is the same + */ + double z_offset_to_carbon_face; + if (useStaves) { + z_offset_to_carbon_face = 0; // stave placed behind the sensor at local z=0 + } else { + z_offset_to_carbon_face = + Constants::foamSpacingThickness / 2.0 + Constants::carbonFiberThickness; + } + double z_offset_to_glue_Ka = + z_offset_to_carbon_face + Constants::epoxyThickness / 2; + double z_offset_to_kapton = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness / 2; + double z_offset_to_copper = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness / 2; + double z_offset_to_glue_Si = + z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness + + Constants::copperThickness + Constants::epoxyThickness / 2; + double z_offset_to_silicon = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness / 2; + + // initialise all y_positions, vector over all staves/columns std::vector y_positionsPosNeg; - // Fill all staves + unsigned volume_count = 0; // give each subvolume a unique ID + // Create the stave volumes and fill the y positions where to put sensors on the stave for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); + const int staveID = Constants::staveIdxToID(i_stave); double y_midpoint = 0.; // default positive and negative starting points has a gap around x-axis - std::pair y_start{0., Constants::sensor2x1_gap}; - const int staveID = Constants::staveIdxToID(i_stave); + std::pair y_start{0., -Constants::sensor2x1_gap}; auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave, use this for starting points y_midpoint = y_midpoint_it->second; // avoid double map lookup double y_start_pos = y_midpoint - Constants::y_lengths[i_stave] / 2; + y_start = {y_start_pos, -y_start_pos}; + } + // only add stave volume if using staves, assume floating otherwise + if (useStaves) { + // Get whether the stave is shifted backward or not + double z_stave_shift_abs = Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; + // now create the stave volume + std::string stave_volume_name = + "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + "_" + std::to_string(direction); + addStaveVolume( + motherVolume, stave_volume_name, direction, &volume_count, + Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], + y_midpoint, mZ + z_stave_shift_abs + ); + if (y_midpoint > Rin) { + // stave midpoint is beyond nominal inner radius, + // so we can reasonably expect to mirror sensors around the x-axis + addStaveVolume( + motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, + Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], + -y_midpoint, mZ + z_stave_shift_abs + ); + } } double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; @@ -358,12 +555,45 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio tolerance, y_start); } - unsigned sensor_count = 0; for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { double x_mid = Constants::x_midpoints[i_stave]; - LOG(info) << "FT3Module: Adding sensor volumes for Stave " << Constants::staveIdxToID(i_stave) + int staveID = Constants::staveIdxToID(i_stave); + LOG(info) << "FT3Module: Adding sensor volumes for Stave " << staveID << " (x = " << x_mid << ") with " << y_positionsPosNeg[i_stave].first.size() << " positive and " << y_positionsPosNeg[i_stave].second.size() << " negative sensor positions."; + /* + * Declare an offset multiplier for the z offsets, used for distinguishing + * sensors facing either forward or backward. + * + * In the new stave layout, all sensors face inward, and isFront + * refers to whether a stave is shifted backwards or not. Thus, we decide + * the offset multiplier only with direction + * + * When using the old slab layout, due to mirroring forward and backward, + * we can simply use isFront, as it is set oppositely depending on forward + * or backward direction earlier, and isFront refers to the face direction. + */ + bool isFront; + if (direction == 1) { // direction = 1 is forward + isFront = Constants::staveOnFront[i_stave]; + } else { + isFront = !(Constants::staveOnFront[i_stave]); + } + + int z_offset_multiplier; + if (useStaves) { + z_offset_multiplier = (direction == 1) ? -1 : 1; + } else { + z_offset_multiplier = isFront ? -1 : 1; + } + + // Get whether the stave is shifted for staggering or not (only when using stave layout) + double z_stave_shift = 0; + if (useStaves && !Constants::staveOnFront[i_stave]) { + // in forward direction, shifting backwards means +z shift + z_stave_shift = (direction == 1) ? Constants::z_offsetStave : -Constants::z_offsetStave; + } + for (unsigned i_y_pos = 0; i_y_pos < y_positionsPosNeg[i_stave].first.size(); i_y_pos++) { for (unsigned i_y_sign = 0; i_y_sign < 2; i_y_sign++) { // place sensors at positive and negative y @@ -376,76 +606,43 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio (Constants::sensor2x1_height + Constants::sensor2x1_gap) * i_sens + Constants::sensor2x1_height / 2; // and add half height to get the middle of the sensor - // get which side we are on: if backward discs we mirror from front so it's the same - // layout from the frame of the particle, regardless which direction - bool isFront; - if (!direction) // direction = 0 is forward - isFront = Constants::staveOnFront[i_stave]; - else - isFront = !(Constants::staveOnFront[i_stave]); - /* - * we build the volume from the outside in, starting with the silicon, - * then glue & materials towards the stave. Depending on whether it's front or back, - * the distance from the center will be mirrored so that we get the following: - * - * Front (ordered in z, assuming the forward direction is to the right): - * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | STAVE | SUPPORT STRUCTURE | - * - * Back (ordered in z, assuming the forward direction is to the right): - * | SUPPORT STRUCTURE | STAVE | GLUE | KAPTON | COPPER | GLUE | SILICON SENSOR | - * - * Note that we do not place stave and support structure material here, that is - * assumed to have been placed by the Layer creation. - */ - double z_offset_centre_to_stave = Constants::foamSpacingThickness / 2.0 + Constants::carbonFiberThickness; - double z_offset_stave_to_silicon = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness - + Constants::epoxyThickness + Constants::siliconThickness / 2; - double z_offset_stave_to_glue_Si = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness - + Constants::epoxyThickness / 2; - double z_offset_stave_to_copper = Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness / 2; - double z_offset_stave_to_kapton = Constants::epoxyThickness + Constants::kaptonThickness / 2; - double z_offset_stave_to_glue_Cu = Constants::epoxyThickness / 2; - - // for the front, we have to subtract the z offsets since we are going in - // negative z direction, while it's opposite for the back - int z_offset_multiplier = isFront ? -1 : 1; std::string side_str = isFront ? "front" : "back"; // ------------ (1) Silicon sensor ------------ // left single sensor of the 2x1 - double z_mid = (z_offset_centre_to_stave + z_offset_stave_to_silicon) * z_offset_multiplier; + double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; addSingleSensorVolume( - motherVolume, layerNumber, direction, &sensor_count, + motherVolume, layerNumber, direction, &volume_count, x_mid - Constants::active_width / 2, y_mid, z_mid, side_str, true ); // right single sensor of the 2x1 addSingleSensorVolume( - motherVolume, layerNumber, direction, &sensor_count, + motherVolume, layerNumber, direction, &volume_count, x_mid + Constants::active_width / 2, y_mid, z_mid, side_str, false ); // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Si) * z_offset_multiplier; + z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( - motherVolume, layerNumber, direction, &sensor_count, + motherVolume, layerNumber, direction, &volume_count, side_str, x_mid, y_mid, z_mid, "SiCu" ); // ------------ (3) Copper layer (FPC) ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_copper) * z_offset_multiplier; + z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift; add2x1CopperVolume( - motherVolume, layerNumber, direction, &sensor_count, + motherVolume, layerNumber, direction, &volume_count, side_str, x_mid, y_mid, z_mid ); // ------------ (4) Kapton layer (FPC) ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_kapton) * z_offset_multiplier; + z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift; add2x1KaptonVolume( - motherVolume, layerNumber, direction, &sensor_count, + motherVolume, layerNumber, direction, &volume_count, side_str, x_mid, y_mid, z_mid ); - // ------------ (5) Epoxy glue layer between stave and FPC copper ------------ - z_mid = (z_offset_centre_to_stave + z_offset_stave_to_glue_Cu) * z_offset_multiplier; + // ------------ (5) Epoxy glue layer between stave and Kapton ------------ + z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( - motherVolume, layerNumber, direction, &sensor_count, - side_str, x_mid, y_mid, z_mid, "StaveKapton" + motherVolume, layerNumber, direction, &volume_count, + side_str, x_mid, y_mid, z_mid, "CarbonKapton" ); } // sensors in stack } // for i_y_sign (writing of positive or negative y positions) @@ -1116,9 +1313,10 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R void FT3Module::createModule_scopingV3(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, - TGeoVolume* motherVolume) { + TGeoVolume* motherVolume, bool useStaves) { LOG(debug) << "FT3Module: createModule_scopingV3 - Layer " << layerNumber - << ", Direction " << direction; - create_layout_scopingV3(mZ, layerNumber, direction, Rin, Rout, overlap, motherVolume); + << " at z=" << mZ << ", Direction " << direction; + create_layout_scopingV3(mZ, layerNumber, direction, Rin, Rout, + overlap, motherVolume, useStaves); LOG(debug) << "FT3Module: done createModule_scopingV3"; } From c74d18e987bc11a8895886fb405296778e9e358a Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 13:33:29 +0200 Subject: [PATCH 10/30] Make Stave layout the standard and remove old kSegmentedMarch26: EDIT 20/04/26: Revert to newly added middle disc layer segmentation. --- .../ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h | 3 +-- Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx | 8 ++------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 84c08e615d33a..ff657ff42f92d 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -24,12 +24,11 @@ enum eFT3Layout { kCylindrical = 0, kTrapezoidal, kSegmented, - kSegmentedMarch26, kSegmentedStave, }; struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - eFT3Layout layoutFT3 = kSegmentedMarch26; + eFT3Layout layoutFT3 = kSegmentedStave; int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 1ba0cf8143f39..d3bf565c8ca13 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -383,8 +383,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); - } else if (ft3Params.layoutFT3 == kSegmented || - ft3Params.layoutFT3 == kSegmentedMarch26 || + } else if (ft3Params.layoutFT3 == kSegmented || ft3Params.layoutFT3 == kSegmentedStave) { FT3Module module; @@ -405,12 +404,9 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) if (ft3Params.layoutFT3 == kSegmented) { module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); - } else if (ft3Params.layoutFT3 == kSegmentedMarch26) { - module.createModule_scopingV3(0., mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, 0., layerVol, false); } else if (ft3Params.layoutFT3 == kSegmentedStave) { module.createModule_scopingV3(0., mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, 0., layerVol, true); + mOuterRadius, 0., layerVol); } // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); From 7ea4b89802950ec4808514f14d4eb8cb3dd9ce29 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 13:34:56 +0200 Subject: [PATCH 11/30] Remove all instances of old slab geometry and work only with staves. --- .../include/FT3Simulation/FT3Module.h | 18 +-- .../FT3Simulation/FT3ModuleConstants.h | 22 ++- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 4 +- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 145 ++++++++---------- 4 files changed, 80 insertions(+), 109 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 9d7f83bbeab47..3b0cb488b1b47 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -52,10 +52,9 @@ class FT3Module double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume); - void createModule_scopingV3( + void createModule_staveGeo( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, TGeoVolume* motherVolume, - bool useStaves); + double Rout, double overlap, TGeoVolume* motherVolume); private: static void create_layout( @@ -63,10 +62,9 @@ class FT3Module double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume); - void create_layout_scopingV3( + void create_layout_staveGeo( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, TGeoVolume* motherVolume, - bool useStaves); + double Rout, double overlap, TGeoVolume* motherVolume); // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rout, @@ -83,20 +81,20 @@ class FT3Module void add2x1GlueVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - std::string side_str, double x_mid, double y_mid, double z_mid, + double x_mid, double y_mid, double z_mid, std::string element_glued_to); void add2x1CopperVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - std::string side_str, double x_mid, double y_mid, double z_mid); + double x_mid, double y_mid, double z_mid); void add2x1KaptonVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - std::string side_str, double x_mid, double y_mid, double z_mid); + double x_mid, double y_mid, double z_mid); void addSingleSensorVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double active_x_mid, double y_mid, double z_mid, std::string side_str, bool isLeft); + double active_x_mid, double y_mid, double z_mid, bool isLeft); }; #endif // FT3MODULE_H diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index 091b9b755a7ec..538c321d1a141 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -56,27 +56,23 @@ namespace o2::ft3::ModuleConstants const unsigned kSensorsPerStack = 1; const double sensor_stack_height = kSensorsPerStack * sensor2x1_height + (kSensorsPerStack - 1) * sensor2x1_gap; - - // OLD VERSION: RECTANGULAR STAVES - const double carbonFiberThickness = 0.01; - const double foamSpacingThickness = 1.0; - /* * Constants for staves are written for both positive * and negative x even though they are just mirrored now, * because there might be design changes in the future * that require a non-mirrored layout, making it easier to * change here if so required, even though it looks uglier now. - */ - // First define midpoints of staves that would overlap with inner disc - // EXCEPTION: Assumed mirrored around x-axis + * + * The second element in the mapping pair is whether the stave + * with a certain ID should be mirrored around the x-axis. + */ // map from Stave ID (1-indexed from other documents) to midpoint // Do NOT add any zero midpoints, this is taken off separately - const std::map staveID_to_y_midpoint = { - {-2, 39.0}, - {-1, 41.4}, - {1, 41.4}, - {2, 39.0} + const std::map> staveID_to_y_midpoint = { + {-2, {39.0, true}}, + {-1, {41.4, true}}, + {1, {41.4, true}}, + {2, {39.0, true}} }; // lengths of staves, their midpoint, and their face const std::vector y_lengths = { diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index d3bf565c8ca13..7dfc5aff957da 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -405,8 +405,8 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); } else if (ft3Params.layoutFT3 == kSegmentedStave) { - module.createModule_scopingV3(0., mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, 0., layerVol); + module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, 0., layerVol); } // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index d7bdde5bb20ed..d47efaf9bea1d 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -321,10 +321,10 @@ void FT3Module::addDetectorVolume( */ void FT3Module::add2x1GlueVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - std::string side_str, double x_mid, double y_mid, double z_mid, + double x_mid, double y_mid, double z_mid, std::string element_glued_to) { - std::string glue_name = "FT3glue_" + element_glued_to + "_" + side_str + "_" + std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(*volume_count); addDetectorVolume( @@ -340,9 +340,9 @@ void FT3Module::add2x1GlueVolume( */ void FT3Module::add2x1CopperVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - std::string side_str, double x_mid, double y_mid, double z_mid) + double x_mid, double y_mid, double z_mid) { - std::string copper_name = "FT3Copper_" + side_str + "_" + std::to_string(layerNumber) + "_" + std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(*volume_count); addDetectorVolume( motherVolume, copper_name, Constants::CuColor, volume_count, @@ -357,9 +357,9 @@ void FT3Module::add2x1CopperVolume( */ void FT3Module::add2x1KaptonVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - std::string side_str, double x_mid, double y_mid, double z_mid) + double x_mid, double y_mid, double z_mid) { - std::string kapton_name = "FT3Kapton_" + side_str + "_" + std::to_string(layerNumber) + "_" + std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(*volume_count); addDetectorVolume( motherVolume, kapton_name, Constants::kaptonColor, volume_count, @@ -384,17 +384,16 @@ void FT3Module::add2x1KaptonVolume( * x_mid: the x position of the center of the sensor volume * y_mid: the y position of the center of the sensor volume * z_mid: the z position of the center of the sensor volume - * side_str: string indicating whether the sensor is on the front or back * isLeft: whether the sensor is on the left or right in the 2x1 layout */ void FT3Module::addSingleSensorVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double active_x_mid, double y_mid, double z_mid, std::string side_str, bool isLeft) + double active_x_mid, double y_mid, double z_mid, bool isLeft) { TGeoVolume* sensor; TGeoManager* geoManager = gGeoManager; // ACTIVE AREA - std::string sensor_name = "FT3Sensor_" + side_str + "_" + std::to_string(layerNumber) + "_" + std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); @@ -414,7 +413,7 @@ void FT3Module::addSingleSensorVolume( double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2) : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); std::string sensor_inactive_name = - "FT3Sensor_Inactive_" + side_str + "_" + std::to_string(layerNumber) + "_" + "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); @@ -432,11 +431,11 @@ void FT3Module::addSingleSensorVolume( (*volume_count)++; } -void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int direction, +void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, - TGeoVolume* motherVolume, bool useStaves) + TGeoVolume* motherVolume) { - LOG(info) << "FT3Module: create_layout_scopingV3 - Layer " + LOG(info) << "FT3Module: create_layout_staveGeo - Layer " << layerNumber << ", Direction " << direction; FT3Module::initialize_materials(); @@ -445,31 +444,18 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio // First let's define some constants used throughout /* * we build the volume from the outside in, starting with the silicon, - * then glue & materials towards the stave/slab. Depending on whether it's front or back, - * the distance from the center will be mirrored so that we get the following: - * - * Front (ordered in z, since the forward direction is to the right): - * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON SLAB | - * - * Back (ordered in z, since the forward direction is to the right): - * | CARBON SLAB | GLUE | KAPTON | COPPER | GLUE | SILICON SENSOR | + * then glue & materials towards the stave. Depending on direction, + * the distance from the center will be mirrored. * - * Note that we do not place stave and support structure material here, that is - * assumed to have been placed by the Layer creation. - * - * kSegmentedStave: With triangular carbon staves we have the same order: * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | + * ----------------------------------------------------------------> z + * + * Naturally, this will be mirrored for layers in the backwards direction, + * such that the face of the sensors always face the interaction region. * - * Thus, we can use same constant names for staves and slabs, - * as the material order is the same + * Currently, we stipulate that the default stave face is at local z=0. */ - double z_offset_to_carbon_face; - if (useStaves) { - z_offset_to_carbon_face = 0; // stave placed behind the sensor at local z=0 - } else { - z_offset_to_carbon_face = - Constants::foamSpacingThickness / 2.0 + Constants::carbonFiberThickness; - } + double z_offset_to_carbon_face = 0; double z_offset_to_glue_Ka = z_offset_to_carbon_face + Constants::epoxyThickness / 2; double z_offset_to_kapton = @@ -495,37 +481,37 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio const int staveID = Constants::staveIdxToID(i_stave); double y_midpoint = 0.; + bool mirrorStaveAroundX = false; // default positive and negative starting points has a gap around x-axis std::pair y_start{0., -Constants::sensor2x1_gap}; auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave, use this for starting points - y_midpoint = y_midpoint_it->second; // avoid double map lookup + y_midpoint = y_midpoint_it->second.first; // avoid double map lookup + mirrorStaveAroundX = y_midpoint_it->second.second; double y_start_pos = y_midpoint - Constants::y_lengths[i_stave] / 2; y_start = {y_start_pos, -y_start_pos}; } - // only add stave volume if using staves, assume floating otherwise - if (useStaves) { - // Get whether the stave is shifted backward or not - double z_stave_shift_abs = Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; - // now create the stave volume - std::string stave_volume_name = - "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + - "_" + std::to_string(direction); + + // Get whether the stave is shifted backward or not + double z_stave_shift_abs = Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; + // now create the stave volume + std::string stave_volume_name = + "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + "_" + std::to_string(direction); + addStaveVolume( + motherVolume, stave_volume_name, direction, &volume_count, + Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], + y_midpoint, mZ + z_stave_shift_abs + ); + if (y_midpoint > Rin) { + // stave midpoint is beyond nominal inner radius, + // so we can reasonably expect to mirror sensors around the x-axis addStaveVolume( - motherVolume, stave_volume_name, direction, &volume_count, + motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], - y_midpoint, mZ + z_stave_shift_abs + -y_midpoint, mZ + z_stave_shift_abs ); - if (y_midpoint > Rin) { - // stave midpoint is beyond nominal inner radius, - // so we can reasonably expect to mirror sensors around the x-axis - addStaveVolume( - motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, - Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], - -y_midpoint, mZ + z_stave_shift_abs - ); - } } double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; @@ -565,13 +551,10 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio * Declare an offset multiplier for the z offsets, used for distinguishing * sensors facing either forward or backward. * - * In the new stave layout, all sensors face inward, and isFront - * refers to whether a stave is shifted backwards or not. Thus, we decide - * the offset multiplier only with direction - * - * When using the old slab layout, due to mirroring forward and backward, - * we can simply use isFront, as it is set oppositely depending on forward - * or backward direction earlier, and isFront refers to the face direction. + * In the stave layout, all sensors face inward, and isFront + * refers to whether a stave is shifted backwards or not. Thus, + * we decide the offset multiplier only with direction, to + * keep the face facing inwards. */ bool isFront; if (direction == 1) { // direction = 1 is forward @@ -579,19 +562,14 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio } else { isFront = !(Constants::staveOnFront[i_stave]); } + int z_offset_multiplier = (direction == 1) ? -1 : 1; - int z_offset_multiplier; - if (useStaves) { - z_offset_multiplier = (direction == 1) ? -1 : 1; - } else { - z_offset_multiplier = isFront ? -1 : 1; - } - - // Get whether the stave is shifted for staggering or not (only when using stave layout) + // Get whether the stave is shifted for staggering or not double z_stave_shift = 0; - if (useStaves && !Constants::staveOnFront[i_stave]) { + if (!Constants::staveOnFront[i_stave]) { // in forward direction, shifting backwards means +z shift - z_stave_shift = (direction == 1) ? Constants::z_offsetStave : -Constants::z_offsetStave; + z_stave_shift = (direction == 1) ? Constants::z_offsetStave + : -Constants::z_offsetStave; } for (unsigned i_y_pos = 0; i_y_pos < y_positionsPosNeg[i_stave].first.size(); i_y_pos++) { @@ -606,43 +584,42 @@ void FT3Module::create_layout_scopingV3(double mZ, int layerNumber, int directio (Constants::sensor2x1_height + Constants::sensor2x1_gap) * i_sens + Constants::sensor2x1_height / 2; // and add half height to get the middle of the sensor - std::string side_str = isFront ? "front" : "back"; // ------------ (1) Silicon sensor ------------ // left single sensor of the 2x1 double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; addSingleSensorVolume( motherVolume, layerNumber, direction, &volume_count, - x_mid - Constants::active_width / 2, y_mid, z_mid, side_str, true + x_mid - Constants::active_width / 2, y_mid, z_mid, true ); // right single sensor of the 2x1 addSingleSensorVolume( motherVolume, layerNumber, direction, &volume_count, - x_mid + Constants::active_width / 2, y_mid, z_mid, side_str, false + x_mid + Constants::active_width / 2, y_mid, z_mid, false ); // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( motherVolume, layerNumber, direction, &volume_count, - side_str, x_mid, y_mid, z_mid, "SiCu" + x_mid, y_mid, z_mid, "SiCu" ); // ------------ (3) Copper layer (FPC) ------------ z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift; add2x1CopperVolume( motherVolume, layerNumber, direction, &volume_count, - side_str, x_mid, y_mid, z_mid + x_mid, y_mid, z_mid ); // ------------ (4) Kapton layer (FPC) ------------ z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift; add2x1KaptonVolume( motherVolume, layerNumber, direction, &volume_count, - side_str, x_mid, y_mid, z_mid + x_mid, y_mid, z_mid ); // ------------ (5) Epoxy glue layer between stave and Kapton ------------ z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( motherVolume, layerNumber, direction, &volume_count, - side_str, x_mid, y_mid, z_mid, "CarbonKapton" + x_mid, y_mid, z_mid, "CarbonKapton" ); } // sensors in stack } // for i_y_sign (writing of positive or negative y positions) @@ -1311,12 +1288,12 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R LOG(debug) << "FT3Module: done createModule"; } -void FT3Module::createModule_scopingV3(double mZ, int layerNumber, int direction, +void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, - TGeoVolume* motherVolume, bool useStaves) { - LOG(debug) << "FT3Module: createModule_scopingV3 - Layer " << layerNumber + TGeoVolume* motherVolume) { + LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber << " at z=" << mZ << ", Direction " << direction; - create_layout_scopingV3(mZ, layerNumber, direction, Rin, Rout, - overlap, motherVolume, useStaves); - LOG(debug) << "FT3Module: done createModule_scopingV3"; + create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout, + overlap, motherVolume); + LOG(debug) << "FT3Module: done createModule_staveGeo"; } From 943541dcf945436946ce683f5525fc1417340686 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 14:00:37 +0200 Subject: [PATCH 12/30] Remove all log(info) statements, except one which is changed to debug. --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 40 +------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index d47efaf9bea1d..3278a8658b758 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -138,15 +138,9 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, max_y_abs = calculate_y_circle(x_left, Rout) - tolerance; } unsigned n_sensors_placed = y_positions.first.size() + y_positions.second.size(); - LOG(info) << "\tFT3Module: Filling stave at x = " << (x_left + Constants::sensor2x1_width / 2) - << " with sensors of height " << sensorTileHeight - << ". Starting positive y position: " << y_top - << ", maximum positive y position: " << max_y_abs - << ", with initially " << n_sensors_placed << " sensors already placed."; while ( (y_top + sensorTileHeight) <= max_y_abs ) { y_positions.first.emplace_back(y_top, kSensorStack); - LOG(info) << "\t\t\tFT3Module: Placed sensor at y = " << y_top; y_top += sensorTileHeight + Constants::sensor2x1_gap; } @@ -160,16 +154,11 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, - Constants::sensor2x1_gap; } - LOG(info) << "\tFT3Module: Starting negative y position: " << y_bottom - << ", minimum negative y position: " << -max_y_abs; while ( (y_bottom - sensorTileHeight) >= -max_y_abs ) { y_positions.second.emplace_back(y_bottom, kSensorStack); - LOG(info) << "\t\t\tFT3Module: Placed sensor at y = " << y_bottom; y_bottom -= (sensorTileHeight + Constants::sensor2x1_gap); } unsigned sensors_placed_after = y_positions.first.size() + y_positions.second.size(); - LOG(info) << "\tFT3Module: Done filling stave. Now have " - << sensors_placed_after << " sensors in total."; } /* @@ -184,11 +173,6 @@ void FT3Module::addStaveVolume( // Set some constants for readability double d = Constants::effectiveCarbonThickness_Stave; double H = Constants::staveTriangleHeight; - LOG(info) << "\tFT3Module: Adding stave volume " << volumeName - << " with count " << *volume_count - << " at (x_mid,y_mid,z_stave_shift_abs) = (" << x_mid << ", " - << y_mid << ", " << z_stave_shift_abs << "), as well as length " - << staveLength << " and triangle height " << H; /* * Inner and outer vertices of the stave cross section triangle * all vertices are at y_mid, we simply extend the triangle into y dir. @@ -255,16 +239,6 @@ void FT3Module::addStaveVolume( staveShape, carbonFiberMed ); - if (*volume_count == 0 && direction == 1) { - LOG(info) << "Outer triangle vertices (x, z): " - << "(" << xv_outer[0] << ", " << zv_outer[0] << "), " - << "(" << xv_outer[1] << ", " << zv_outer[1] << "), " - << "(" << xv_outer[2] << ", " << zv_outer[2] << ")"; - LOG(info) << "Inner triangle vertices (x, z): " - << "(" << xv_inner[0] << ", " << zv_inner[0] << "), " - << "(" << xv_inner[1] << ", " << zv_inner[1] << "), " - << "(" << xv_inner[2] << ", " << zv_inner[2] << ")"; - } TGeoRotation* rot = new TGeoRotation(); rot->RotateX(-90); // lift from xy plane into xz plane /* @@ -281,11 +255,6 @@ void FT3Module::addStaveVolume( *volume_count, combiTrans); (*volume_count)++; - // print: - TGeoNode* node = motherVolume->GetNode(motherVolume->GetNdaughters() - 1); - const Double_t* translation = node->GetMatrix()->GetTranslation(); - LOG(info) << "\t\tStave node position: (" << translation[0] << ", " - << translation[1] << ", " << translation[2] << ")"; } /* @@ -435,7 +404,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction double Rin, double Rout, double overlap, TGeoVolume* motherVolume) { - LOG(info) << "FT3Module: create_layout_staveGeo - Layer " + LOG(debug) << "FT3Module: create_layout_staveGeo - Layer " << layerNumber << ", Direction " << direction; FT3Module::initialize_materials(); @@ -533,10 +502,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction y_start = {min_y_at_x, -min_y_at_x}; tolerance = 0.; // no tolerance in case of cutting at nominal radius } - // fill_stave(y_positionsPosNeg, Rout, x_left, Constants::kSensorsPerStack, -3); - LOG(info) << "FT3Module: Filling Stave " << staveID << " (x = " - << Constants::x_midpoints[i_stave] << ") with sensors. Starting y positions: " - << y_start.first << " (positive), " << y_start.second << " (negative)."; fill_stave(y_positionsPosNeg.back(), Rout, x_left, Constants::kSensorsPerStack, tolerance, y_start); } @@ -544,9 +509,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { double x_mid = Constants::x_midpoints[i_stave]; int staveID = Constants::staveIdxToID(i_stave); - LOG(info) << "FT3Module: Adding sensor volumes for Stave " << staveID - << " (x = " << x_mid << ") with " << y_positionsPosNeg[i_stave].first.size() << " positive and " - << y_positionsPosNeg[i_stave].second.size() << " negative sensor positions."; /* * Declare an offset multiplier for the z offsets, used for distinguishing * sensors facing either forward or backward. From fba45ba273eaf25a29249b30fd77415d680e6aa8 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 7 Apr 2026 15:08:15 +0200 Subject: [PATCH 13/30] Don't create the separation layer for stave layout, since we already get the structural support (in carbon) from the staves --- Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 7dfc5aff957da..84347c6b8d202 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -397,9 +397,10 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow + 2); - // createSeparationLayer_waterCooling(motherVolume, separationLayerName); - createSeparationLayer(layerVol, separationLayerName); - + if (ft3Params.layoutFT3 == kSegmented) { + // createSeparationLayer_waterCooling(motherVolume, separationLayerName); + createSeparationLayer(layerVol, separationLayerName); + } // create disk faces if (ft3Params.layoutFT3 == kSegmented) { module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); From 48934e724acd055726bb32ccc07d28b501686fa0 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 9 Apr 2026 13:57:27 +0200 Subject: [PATCH 14/30] add bools for cutting staves and sensors on staves on nominal radii --- .../Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index ff657ff42f92d..3962860038d8d 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -39,8 +39,9 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { Float_t etaOut = 1.5; Float_t Layerx2X0 = 0.01; - // override values from FT3ModuleConstants - bool cutStavesOnNominalRadius = false; + // override values from FT3ModuleConstants, inner and outer + bool cutStavesOnNominalRadius_inner = false; + bool cutStavesOnNominalRadius_outer = false; O2ParamDef(FT3BaseParam, "FT3Base"); }; From 88d69a0cdf6897bc5ed5073b5a10b08798bdefde Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 9 Apr 2026 13:59:09 +0200 Subject: [PATCH 15/30] merge two if statements with the same if --- Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx | 3 --- 1 file changed, 3 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 84347c6b8d202..d3db1660adc90 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -400,9 +400,6 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) if (ft3Params.layoutFT3 == kSegmented) { // createSeparationLayer_waterCooling(motherVolume, separationLayerName); createSeparationLayer(layerVol, separationLayerName); - } - // create disk faces - if (ft3Params.layoutFT3 == kSegmented) { module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); } else if (ft3Params.layoutFT3 == kSegmentedStave) { From 948e308c9bda3ac7e0cdc2042b141cb2d2df1267 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 9 Apr 2026 14:01:32 +0200 Subject: [PATCH 16/30] Add implementations for cutting staves and sensors on nominal radii. Also change kSensorsPerStack to a vector in which order of sensor stack height we will pad the staves. --- .../include/FT3Simulation/FT3Module.h | 11 +- .../FT3Simulation/FT3ModuleConstants.h | 9 +- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 222 ++++++++++++------ 3 files changed, 169 insertions(+), 73 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 3b0cb488b1b47..821f9ea2bb3a4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -25,6 +25,8 @@ using PositionType = std::pair; using PositionTypes = std::vector; using PosNegPositionTypes = std::pair; +// define type of the y position range: First pair is (min, max) for positive y +using PositionRangeType = std::pair, std::pair>; namespace Constants = o2::ft3::ModuleConstants; class FT3Module @@ -67,12 +69,13 @@ class FT3Module double Rout, double overlap, TGeoVolume* motherVolume); // Helper functions - void fill_stave(PosNegPositionTypes& y_positions, double Rout, - double x_left, unsigned kSensorStack, double tolerance, - std::pair y_start); + void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, + double x_left, unsigned kSensorStack, double tolerance_inner, + double tolerance_outer, PositionRangeType y_range); void addStaveVolume( TGeoVolume* motherVolume, std::string volumeName, int direction, - unsigned* volume_count, double staveLength, + unsigned* volume_count, double staveLength, double tolerance_inner, + double tolerance_outer, double Rin, double Rout, double x_mid, double y_mid, double z_stave_shift_abs); void addDetectorVolume( TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index 538c321d1a141..4b38441775355 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -46,6 +46,7 @@ namespace o2::ft3::ModuleConstants const double single_sensor_height = 3.2; const double inactive_width = 0.2; const double sensor2x1_gap = 0.02; + const double stackGap = sensor2x1_gap; // gap between 2xN module stacks const double active_width = single_sensor_width - inactive_width; const double active_height = single_sensor_height; @@ -53,9 +54,11 @@ namespace o2::ft3::ModuleConstants const double sensor2x1_width = 2 * single_sensor_width; const double sensor2x1_active_width = 2 * active_width; const double sensor2x1_height = single_sensor_height; - const unsigned kSensorsPerStack = 1; - const double sensor_stack_height = kSensorsPerStack * sensor2x1_height + - (kSensorsPerStack - 1) * sensor2x1_gap; + const std::vector kSensorsPerStack = {4,2,1}; + inline const double getStackHeight(unsigned nSensorsPerStack) { + return nSensorsPerStack * sensor2x1_height + + (nSensorsPerStack - 1) * sensor2x1_gap; + } /* * Constants for staves are written for both positive * and negative x even though they are just mirrored now, diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 3278a8658b758..b19e81323da9e 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -93,6 +93,44 @@ double calculate_y_circle(double x, double radius) return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0; } +std::pair calculate_y_range( + double x_left, double x_right, double Rin, double Rout) +{ + double max_y_abs; + double min_y_abs; + /* + * Have 5 cases: + * (1) Stave wholly on the left of inner radius + * (2) Stave wholly on the left, but within inner radius + * (3) Stave crosses the middle x=0 + * (4) Stave wholly on the right, but within inner radius + * (5) Stave wholly on the right of inner radius + */ + if (x_right < -Rin) { + // Stave is completely on the left of inner radius + min_y_abs = 0; + max_y_abs = calculate_y_circle(x_left, Rout); + } else if (x_left < -Constants::sensor2x1_width) { + // Stave is completely on the left, but within inner radius + min_y_abs = calculate_y_circle(x_right, Rin); + max_y_abs = calculate_y_circle(x_left, Rout); + } else if (x_left < 0) { + // Stave crosses the middle x=0 + min_y_abs = Rin; + // x_right should be > 0, but might have FLP issues, so do abs nonetheless + max_y_abs = calculate_y_circle(std::max(std::abs(x_left), std::abs(x_right)), Rout); + } else if (x_left < Rin) { + // Stave is completely on the right, but within inner radius + min_y_abs = calculate_y_circle(x_left, Rin); + max_y_abs = calculate_y_circle(x_right, Rout); + } else { + // Stave is completely on the right of inner radius + min_y_abs = 0.; + max_y_abs = calculate_y_circle(x_right, Rout); + } + return {min_y_abs, max_y_abs}; +} + /* * This function is a helper function which will pad out the stave with sensors * until there is no more space available. @@ -111,54 +149,60 @@ double calculate_y_circle(double x, double radius) * y_start: the y positions to start placing sensors, * for positive and negative y respectively */ -void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, - double x_left, unsigned kSensorStack, double tolerance, - std::pair y_start={0, 0}) +void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, + double x_left, unsigned kSensorStack, double tolerance_inner, + double tolerance_outer, PositionRangeType y_ranges) { // start with upper half of the stave, then mirror to the bottom half double x_right = x_left + Constants::sensor2x1_width; - double y_top = y_start.first; - // either start at given start position, or at the top of the last placed sensors - if (!y_positions.first.empty()) { - y_top = y_positions.first.back().first - + Constants::sensor2x1_height * y_positions.first.back().second - + Constants::sensor2x1_gap; - } // add the height of kSensorStack sensors + the gaps in between them - double sensorTileHeight = Constants::sensor2x1_height * kSensorStack - + Constants::sensor2x1_gap * (kSensorStack - 1); + double sensorStackHeight = Constants::getStackHeight(kSensorStack); + double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap; - double max_y_abs; - // y_max(x_left) > y_max(x_right) means that the top of the sensor - // will hit the outer radius at x_right first - if (x_left > -Constants::single_sensor_width) { - // tolerance already in maximum y position - max_y_abs = calculate_y_circle(x_right, Rout) - tolerance; + std::pair absAllowedYRange = + calculate_y_range(x_left, x_right, Rin, Rout); + + // shift allowed range by tolerance. Note that this sum can be negative, but + // that is not a problem, it just means that we can always place sensors over the edge + absAllowedYRange.first += tolerance_inner; + absAllowedYRange.second -= tolerance_outer; + // in case a big tolerance is given, cut on the given range instead + double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second); + + double y_top; // top half of the xy grid + // either start at given value (adjusted for tolerance), or at last placed sensors + if (!y_positions.first.empty()) { // sensors already placed + y_top = y_positions.first.back().first + sensorAbsStackYShift; + } else if (y_ranges.first.first < absAllowedYRange.first) { + // given starting y is lower than the minimum allowed y, start at the latter. + y_top = absAllowedYRange.first; } else { - max_y_abs = calculate_y_circle(x_left, Rout) - tolerance; + // given starting y is acceptable, start there + y_top = y_ranges.first.first; } - unsigned n_sensors_placed = y_positions.first.size() + y_positions.second.size(); - - while ( (y_top + sensorTileHeight) <= max_y_abs ) { + while ( (y_top + sensorStackHeight) <= max_sensor_y_abs ) { y_positions.first.emplace_back(y_top, kSensorStack); - y_top += sensorTileHeight + Constants::sensor2x1_gap; + y_top += sensorAbsStackYShift; } // now we do the same for the negative y positions // they do not have to be exactly mirrored, hence done separately - double y_bottom = y_start.second; + double y_bottom; if (!y_positions.second.empty()) { // subtract instead to move further down - y_bottom = y_positions.second.back().first - - Constants::sensor2x1_height * y_positions.second.back().second - - Constants::sensor2x1_gap; + y_bottom = y_positions.second.back().first - sensorAbsStackYShift; + } else if (y_ranges.second.first > -absAllowedYRange.first) { + // given starting y is closer to x-axis than max allowed y, start at the latter. + y_bottom = -absAllowedYRange.first; + } else { + // given starting y is acceptable, start there + y_bottom = y_ranges.second.first; } - - while ( (y_bottom - sensorTileHeight) >= -max_y_abs ) { + // fill in the sensors on negative y + while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) { y_positions.second.emplace_back(y_bottom, kSensorStack); - y_bottom -= (sensorTileHeight + Constants::sensor2x1_gap); + y_bottom -= sensorAbsStackYShift; } - unsigned sensors_placed_after = y_positions.first.size() + y_positions.second.size(); } /* @@ -167,7 +211,8 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rout, */ void FT3Module::addStaveVolume( TGeoVolume* motherVolume, std::string volumeName, int direction, - unsigned* volume_count, double staveLength, + unsigned* volume_count, double staveLength, double tolerance_inner, + double tolerance_outer, double Rin, double Rout, double x_mid, double y_mid, double z_stave_shift_abs) { // Set some constants for readability @@ -215,20 +260,48 @@ void FT3Module::addStaveVolume( zv_inner[2] = zv_inner[1]; xv_inner[2] = -xv_inner[1]; - // create the extruded volumes from z=0 (later y=0 after rotation) to stave length - // and not from midpoint - staveLength/2 to midpoint + staveLength/2, - // translate after rotation + /* + * create the extruded volumes from z=0 (later y=0 after rotation) to stave length + * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later + * + * Note also that we first need to check if the length is allowed given the inner + * and outer radius of the layer. + */ + double x_left = x_mid - Constants::single_sensor_width; + double x_right = x_mid + Constants::single_sensor_width; + std::pair absAllowedYRange = + calculate_y_range(x_left, x_right, Rin, Rout); + + // shift allowed range by tolerance. Note that this sum can be negative, but + // that is not a problem, it just means that we can always place staves over the edge + absAllowedYRange.first += tolerance_inner; + absAllowedYRange.second -= tolerance_outer; + + double maxStaveLength = absAllowedYRange.second - absAllowedYRange.first; + double staveLengthToUse = std::min(staveLength, maxStaveLength); + if (staveLengthToUse <= 0) { + LOG(warning) << "Stave " << volumeName << " has non-positive length after applying " + << "tolerance, skipping stave. Max allowed length: " << maxStaveLength + << " tolerance inner: " << tolerance_inner + << " tolerance outer: " << tolerance_outer; + return; + } + double staveLengthDiff = staveLength - staveLengthToUse; + if (staveLengthDiff > 0) { // had to cut it, hence y_mid must move too + y_mid = absAllowedYRange.first + staveLengthToUse / 2; + } + TGeoXtru* staveFull = new TGeoXtru(2); staveFull->SetName(( volumeName + "_Xtru_outer").c_str()); staveFull->DefinePolygon(3, xv_outer, zv_outer); staveFull->DefineSection(0, 0); - staveFull->DefineSection(1, staveLength); + staveFull->DefineSection(1, staveLengthToUse); TGeoXtru* staveInner = new TGeoXtru(2); staveInner->SetName(( volumeName + "_Xtru_inner").c_str()); staveInner->DefinePolygon(3, xv_inner, zv_inner); staveInner->DefineSection(0, 0); - staveInner->DefineSection(1, staveLength); + staveInner->DefineSection(1, staveLengthToUse); TGeoCompositeShape* staveShape = new TGeoCompositeShape( (volumeName + "_shape").c_str(), @@ -250,7 +323,7 @@ void FT3Module::addStaveVolume( */ double z_shift = (direction == 1) ? z_stave_shift_abs : -z_stave_shift_abs; TGeoCombiTrans* combiTrans = - new TGeoCombiTrans(x_mid, y_mid - staveLength / 2, z_shift, rot); + new TGeoCombiTrans(x_mid, y_mid - staveLengthToUse / 2, z_shift, rot); motherVolume->AddNode(staveVolume, *volume_count, combiTrans); @@ -452,14 +525,27 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction double y_midpoint = 0.; bool mirrorStaveAroundX = false; // default positive and negative starting points has a gap around x-axis - std::pair y_start{0., -Constants::sensor2x1_gap}; + PositionRangeType y_ranges = {{0, Constants::y_lengths[i_stave]}, + {-Constants::stackGap, -Constants::y_lengths[i_stave]}}; auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave, use this for starting points y_midpoint = y_midpoint_it->second.first; // avoid double map lookup mirrorStaveAroundX = y_midpoint_it->second.second; - double y_start_pos = y_midpoint - Constants::y_lengths[i_stave] / 2; - y_start = {y_start_pos, -y_start_pos}; + double y_diff_abs = Constants::y_lengths[i_stave] / 2; + y_ranges.first = {y_midpoint - y_diff_abs, y_midpoint + y_diff_abs}; + y_ranges.second = {-y_midpoint + y_diff_abs, -y_midpoint - y_diff_abs}; + } + + // Define tolerances for cutting staves and placing sensors + double tolerance_inner = -1000; // large negative number to allow given numbers + double tolerance_outer = -1000; + // cut staves on nominal inner radius if specified + if (ft3Params.cutStavesOnNominalRadius_inner) { + tolerance_inner = 0.; + } + if (ft3Params.cutStavesOnNominalRadius_outer) { + tolerance_outer = 0.; } // Get whether the stave is shifted backward or not @@ -470,40 +556,44 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction "_" + std::to_string(direction); addStaveVolume( motherVolume, stave_volume_name, direction, &volume_count, - Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], + Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, + Rin, Rout, Constants::x_midpoints[i_stave], y_midpoint, mZ + z_stave_shift_abs ); - if (y_midpoint > Rin) { - // stave midpoint is beyond nominal inner radius, - // so we can reasonably expect to mirror sensors around the x-axis + /* + * There are three cases in which we want to mirror the stave around the x-axis, + * which correspond to the stave not going fully from + to - Rout in y. + * + * (1) The inner tolerance is 0 (or positive) + * a) AND either x_left or x_right lies within the inner radius + * (2) The inner tolerance is large (allow stave placement as wished) + * a) AND the given stave midpoint is above the inner radius + */ + double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; + double x_right = x_left + Constants::sensor2x1_width; + bool mirrorStaveAroundXAxis = false; + if (tolerance_inner >= 0) { + double x_innermost = std::min(std::abs(x_left), std::abs(x_right)); + mirrorStaveAroundXAxis = (x_innermost < Rin); + } else { // Have negative tolerance, so can place staves as wished + mirrorStaveAroundXAxis = (y_midpoint > Rin); + } + // Now create the mirrored stave + if (mirrorStaveAroundXAxis) { addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, - Constants::y_lengths[i_stave], Constants::x_midpoints[i_stave], + Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, + Rin, Rout, Constants::x_midpoints[i_stave], -y_midpoint, mZ + z_stave_shift_abs ); } - double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; - double x_right = x_left + Constants::sensor2x1_width; - double tolerance = -Constants::sensor_stack_height; // allow one sensor placement beyond - // cut staves on nominal inner radius if specified - if (ft3Params.cutStavesOnNominalRadius) { - double min_y_at_x; - if (x_left * x_right < 0) { - // stave crosses y-axis, so we start at y=Rin - min_y_at_x = Rin; - } else if (x_left > 0) { - // stave is on the right side, so minimum y is at x_left - min_y_at_x = calculate_y_circle(x_left, Rin); - } else { - // stave is on the left side, so minimum y is at x_right - min_y_at_x = calculate_y_circle(x_right, Rin); - } - y_start = {min_y_at_x, -min_y_at_x}; - tolerance = 0.; // no tolerance in case of cutting at nominal radius + // now add the sensor positions on the stave + for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) { + fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left, + Constants::kSensorsPerStack[i_kSens], tolerance_inner, + tolerance_outer, y_ranges); } - fill_stave(y_positionsPosNeg.back(), Rout, x_left, Constants::kSensorsPerStack, - tolerance, y_start); } for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { From 02284a8ad2b5c9b61c158d0c5a4e50051d5998a4 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Thu, 9 Apr 2026 15:22:03 +0200 Subject: [PATCH 17/30] Fix bug in default y range for staves & sensor placements --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index b19e81323da9e..2f1b1d53d3741 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -524,17 +524,18 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction double y_midpoint = 0.; bool mirrorStaveAroundX = false; - // default positive and negative starting points has a gap around x-axis - PositionRangeType y_ranges = {{0, Constants::y_lengths[i_stave]}, - {-Constants::stackGap, -Constants::y_lengths[i_stave]}}; + // default positive and negative starting points has a gap around x-axis for symmetry + double stave_half_length = Constants::y_lengths[i_stave] / 2; + PositionRangeType y_ranges = + {{Constants::stackGap / 2, stave_half_length}, + {-Constants::stackGap / 2, -stave_half_length}}; auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave, use this for starting points y_midpoint = y_midpoint_it->second.first; // avoid double map lookup mirrorStaveAroundX = y_midpoint_it->second.second; - double y_diff_abs = Constants::y_lengths[i_stave] / 2; - y_ranges.first = {y_midpoint - y_diff_abs, y_midpoint + y_diff_abs}; - y_ranges.second = {-y_midpoint + y_diff_abs, -y_midpoint - y_diff_abs}; + y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; + y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; } // Define tolerances for cutting staves and placing sensors From 99a136e00d3293af4bfdf5982977f7d367e1c4ce Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Fri, 10 Apr 2026 11:26:49 +0200 Subject: [PATCH 18/30] Bugfix: Stack correctly by using previous stack height in fill_stave, and move in correct direction when placing sensors later --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 2f1b1d53d3741..a850f8b9649f8 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -169,10 +169,11 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double // in case a big tolerance is given, cut on the given range instead double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second); - double y_top; // top half of the xy grid + double y_top; // top half of the xy grid, y>0 // either start at given value (adjusted for tolerance), or at last placed sensors if (!y_positions.first.empty()) { // sensors already placed - y_top = y_positions.first.back().first + sensorAbsStackYShift; + double previousStackHeight = Constants::getStackHeight(y_positions.first.back().second); + y_top = y_positions.first.back().first + previousStackHeight + Constants::stackGap; } else if (y_ranges.first.first < absAllowedYRange.first) { // given starting y is lower than the minimum allowed y, start at the latter. y_top = absAllowedYRange.first; @@ -180,17 +181,21 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double // given starting y is acceptable, start there y_top = y_ranges.first.first; } + LOG(info) << "\tFT3Module: Filling stave at x = " << x_left << " with sensors starting at y = " << y_top + << " with stack height " << kSensorStack; while ( (y_top + sensorStackHeight) <= max_sensor_y_abs ) { y_positions.first.emplace_back(y_top, kSensorStack); y_top += sensorAbsStackYShift; } + LOG(info) << "\t\tFilled until y = " << y_top; // now we do the same for the negative y positions // they do not have to be exactly mirrored, hence done separately double y_bottom; if (!y_positions.second.empty()) { // subtract instead to move further down - y_bottom = y_positions.second.back().first - sensorAbsStackYShift; + double previousStackHeight = Constants::getStackHeight(y_positions.second.back().second); + y_bottom = y_positions.second.back().first - previousStackHeight - Constants::stackGap; } else if (y_ranges.second.first > -absAllowedYRange.first) { // given starting y is closer to x-axis than max allowed y, start at the latter. y_bottom = -absAllowedYRange.first; @@ -198,11 +203,14 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double // given starting y is acceptable, start there y_bottom = y_ranges.second.first; } + LOG(info) << "\tFT3Module: Filling stave at x = " << x_left << " with sensors starting at y = " << y_bottom + << " with stack height " << kSensorStack; // fill in the sensors on negative y while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) { y_positions.second.emplace_back(y_bottom, kSensorStack); y_bottom -= sensorAbsStackYShift; } + LOG(info) << "\t\tFilled until y = " << y_bottom; } /* @@ -328,6 +336,9 @@ void FT3Module::addStaveVolume( *volume_count, combiTrans); (*volume_count)++; + LOG(info) << "\tFT3Module: Added stave volume " << volumeName + << " at x = " << x_mid << ", y = " << y_mid << ", z = " << z_shift + << " with length " << staveLengthToUse; } /* @@ -536,6 +547,9 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction mirrorStaveAroundX = y_midpoint_it->second.second; y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; + LOG(info) << "Stave " << staveID << " has defined midpoint at y = " << y_midpoint + << ", y_ranges=(" << y_ranges.first.first << ", " << y_ranges.first.second + << ") & (" << y_ranges.second.first << ", " << y_ranges.second.second << ")"; } // Define tolerances for cutting staves and placing sensors @@ -555,6 +569,9 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction std::string stave_volume_name = "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction); + LOG(info) << "\tFT3Module: Adding stave volume " << stave_volume_name + << " at x = " << Constants::x_midpoints[i_stave] << ", y = " << y_midpoint + << ", z = " << mZ + z_stave_shift_abs; addStaveVolume( motherVolume, stave_volume_name, direction, &volume_count, Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, @@ -581,6 +598,9 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } // Now create the mirrored stave if (mirrorStaveAroundXAxis) { + LOG(info) << "\tFT3Module: Adding stave volume " << stave_volume_name + << " at x = " << Constants::x_midpoints[i_stave] << ", y = " << y_midpoint + << ", z = " << mZ + z_stave_shift_abs; addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, @@ -626,17 +646,14 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } for (unsigned i_y_pos = 0; i_y_pos < y_positionsPosNeg[i_stave].first.size(); i_y_pos++) { - for (unsigned i_y_sign = 0; i_y_sign < 2; i_y_sign++) { + for (int y_sign = -1; y_sign < 2; y_sign+=2) { // place sensors at positive and negative y - const auto& positions = (i_y_sign == 0) ? y_positionsPosNeg[i_stave].first + const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first : y_positionsPosNeg[i_stave].second; + // define starting midpoint: y = y_start +- distance to middle of sensor + double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2; for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) { TGeoVolume* sensor; - double y_mid = // y = y_start + (height + gap of one sensor) * sensor index - positions[i_y_pos].first + - (Constants::sensor2x1_height + Constants::sensor2x1_gap) * i_sens + - Constants::sensor2x1_height / 2; // and add half height to get the middle of the sensor - // ------------ (1) Silicon sensor ------------ // left single sensor of the 2x1 double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; @@ -649,6 +666,11 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction motherVolume, layerNumber, direction, &volume_count, x_mid + Constants::active_width / 2, y_mid, z_mid, false ); + if (i_stave == 3) { + LOG(info) << "\tFT3Module: Adding sensor on " + << (y_sign == 0 ? "positive" : "negative") << " y side, sensor " + << i_sens << " out of " << positions[i_y_pos].second << " at y = " << y_mid; + } // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; @@ -674,8 +696,10 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction motherVolume, layerNumber, direction, &volume_count, x_mid, y_mid, z_mid, "CarbonKapton" ); + // increment to next sensor: (height + gap of one sensor) + y_mid += y_sign * (Constants::sensor2x1_height + Constants::sensor2x1_gap); } // sensors in stack - } // for i_y_sign (writing of positive or negative y positions) + } // for y_sign (writing of positive or negative y positions) } // i_y_pos } // i_stave From 03bf67c23b1c976ee8557e2a54757124b2b672e9 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Fri, 10 Apr 2026 11:30:36 +0200 Subject: [PATCH 19/30] Remove now stale info statements. TODO: let staves be cut as well on nominal radii --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index a850f8b9649f8..1117906193c87 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -181,13 +181,11 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double // given starting y is acceptable, start there y_top = y_ranges.first.first; } - LOG(info) << "\tFT3Module: Filling stave at x = " << x_left << " with sensors starting at y = " << y_top - << " with stack height " << kSensorStack; + // fill positive y sensor positions while ( (y_top + sensorStackHeight) <= max_sensor_y_abs ) { y_positions.first.emplace_back(y_top, kSensorStack); y_top += sensorAbsStackYShift; } - LOG(info) << "\t\tFilled until y = " << y_top; // now we do the same for the negative y positions // they do not have to be exactly mirrored, hence done separately @@ -203,14 +201,11 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double // given starting y is acceptable, start there y_bottom = y_ranges.second.first; } - LOG(info) << "\tFT3Module: Filling stave at x = " << x_left << " with sensors starting at y = " << y_bottom - << " with stack height " << kSensorStack; // fill in the sensors on negative y while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) { y_positions.second.emplace_back(y_bottom, kSensorStack); y_bottom -= sensorAbsStackYShift; } - LOG(info) << "\t\tFilled until y = " << y_bottom; } /* @@ -336,9 +331,6 @@ void FT3Module::addStaveVolume( *volume_count, combiTrans); (*volume_count)++; - LOG(info) << "\tFT3Module: Added stave volume " << volumeName - << " at x = " << x_mid << ", y = " << y_mid << ", z = " << z_shift - << " with length " << staveLengthToUse; } /* @@ -547,9 +539,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction mirrorStaveAroundX = y_midpoint_it->second.second; y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; - LOG(info) << "Stave " << staveID << " has defined midpoint at y = " << y_midpoint - << ", y_ranges=(" << y_ranges.first.first << ", " << y_ranges.first.second - << ") & (" << y_ranges.second.first << ", " << y_ranges.second.second << ")"; } // Define tolerances for cutting staves and placing sensors @@ -569,9 +558,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction std::string stave_volume_name = "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction); - LOG(info) << "\tFT3Module: Adding stave volume " << stave_volume_name - << " at x = " << Constants::x_midpoints[i_stave] << ", y = " << y_midpoint - << ", z = " << mZ + z_stave_shift_abs; addStaveVolume( motherVolume, stave_volume_name, direction, &volume_count, Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, @@ -598,9 +584,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } // Now create the mirrored stave if (mirrorStaveAroundXAxis) { - LOG(info) << "\tFT3Module: Adding stave volume " << stave_volume_name - << " at x = " << Constants::x_midpoints[i_stave] << ", y = " << y_midpoint - << ", z = " << mZ + z_stave_shift_abs; addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, @@ -666,12 +649,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction motherVolume, layerNumber, direction, &volume_count, x_mid + Constants::active_width / 2, y_mid, z_mid, false ); - if (i_stave == 3) { - LOG(info) << "\tFT3Module: Adding sensor on " - << (y_sign == 0 ? "positive" : "negative") << " y side, sensor " - << i_sens << " out of " << positions[i_y_pos].second << " at y = " << y_mid; - } - // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( From f74b29164d05bd22f0712ce726ea4b31bb2b965f Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Mon, 13 Apr 2026 14:26:32 +0200 Subject: [PATCH 20/30] Add splitting of stave in case of strict inner cutoff --- .../include/FT3Simulation/FT3Module.h | 9 +- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 180 +++++++++++------- 2 files changed, 116 insertions(+), 73 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 821f9ea2bb3a4..096f432efaa4f 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -70,12 +70,13 @@ class FT3Module // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, - double x_left, unsigned kSensorStack, double tolerance_inner, - double tolerance_outer, PositionRangeType y_range); + double x_left, unsigned kSensorStack, PositionRangeType y_range, + std::pair& absAllowedYRange); void addStaveVolume( TGeoVolume* motherVolume, std::string volumeName, int direction, - unsigned* volume_count, double staveLength, double tolerance_inner, - double tolerance_outer, double Rin, double Rout, + unsigned* volume_count, double staveLength, + std::array, 4> staveTriangles, + std::pair& absAllowedYRange, double x_mid, double y_mid, double z_stave_shift_abs); void addDetectorVolume( TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 1117906193c87..9fa0e337980dc 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -150,22 +150,14 @@ std::pair calculate_y_range( * for positive and negative y respectively */ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, - double x_left, unsigned kSensorStack, double tolerance_inner, - double tolerance_outer, PositionRangeType y_ranges) + double x_left, unsigned kSensorStack, PositionRangeType y_ranges, + std::pair& absAllowedYRange) { // start with upper half of the stave, then mirror to the bottom half - double x_right = x_left + Constants::sensor2x1_width; // add the height of kSensorStack sensors + the gaps in between them double sensorStackHeight = Constants::getStackHeight(kSensorStack); double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap; - std::pair absAllowedYRange = - calculate_y_range(x_left, x_right, Rin, Rout); - - // shift allowed range by tolerance. Note that this sum can be negative, but - // that is not a problem, it just means that we can always place sensors over the edge - absAllowedYRange.first += tolerance_inner; - absAllowedYRange.second -= tolerance_outer; // in case a big tolerance is given, cut on the given range instead double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second); @@ -209,14 +201,14 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double } /* - * This function creates a carbon fibre volume for the stave, - * onto which the sensor and its support will be glued. + * Create the vertices of the triangles that make up the stave cross section + * + * Each array of 3 corresponds to x or z values of the 3 triangle vertices, + * and the outer array corresponds to which triangle: + * + * [x_outer, z_outer, x_inner, z_inner], each of which has three values */ -void FT3Module::addStaveVolume( - TGeoVolume* motherVolume, std::string volumeName, int direction, - unsigned* volume_count, double staveLength, double tolerance_inner, - double tolerance_outer, double Rin, double Rout, - double x_mid, double y_mid, double z_stave_shift_abs) +std::array, 4> buildStaveTriangle(int direction) { // Set some constants for readability double d = Constants::effectiveCarbonThickness_Stave; @@ -234,7 +226,7 @@ void FT3Module::addStaveVolume( * 4. Translate the volume to the given position (arguments) * */ - double xv_inner[3], xv_outer[3], zv_inner[3], zv_outer[3]; + std::array xv_inner, xv_outer, zv_inner, zv_outer; // calculate the coordinates of the triangle vertices // Top/bottom vertex (apex) xv_outer[0] = 0; @@ -263,6 +255,52 @@ void FT3Module::addStaveVolume( zv_inner[2] = zv_inner[1]; xv_inner[2] = -xv_inner[1]; + return {xv_outer, zv_outer, xv_inner, zv_inner}; +} + +/* + * This function creates a carbon fibre volume for the stave, + * onto which the sensor and its support will be glued. + */ +void FT3Module::addStaveVolume( + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + std::array, 4> staveTriangles, + std::pair& absAllowedYRange, + double x_mid, double y_mid, double z_stave_shift_abs) +{ + // The allowed y range is assumed to be non-negative. + if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 || + absAllowedYRange.first >= absAllowedYRange.second) { + LOG(error) << "Invalid allowed y range in addStaveVolume(): (" + << absAllowedYRange.first << ", " << absAllowedYRange.second + << "). Both values must be non-negative and the first " + << "value must be less than the second value."; + return; + } + // Set the lower and upper y values of the stave: + double y_lower = y_mid - staveLength / 2; + double y_upper = y_mid + staveLength / 2; + bool splitStave = false; + if (y_lower > 0) { // This stave is fully above x-axis + y_lower = std::max(y_lower, absAllowedYRange.first); + y_upper = std::min(y_upper, absAllowedYRange.second); + } else if (y_upper < 0) { // stave entirely below x-axis + y_lower = std::max(y_lower, -absAllowedYRange.second); + y_upper = std::min(y_upper, -absAllowedYRange.first); + } else { // Full range stave that goes across x-axis + // Here we might have to cut the stave up into two pieces + if (absAllowedYRange.first > 0) { + // There is a minimum inner value --> Split stave + splitStave = true; + y_lower = absAllowedYRange.first; + } else { + // regular stave, use full length, but don't forget outer cut + y_lower = std::max(y_lower, -absAllowedYRange.second); + } + y_upper = std::min(y_upper, absAllowedYRange.second); + } + double staveLengthToUse = y_upper - y_lower; /* * create the extruded volumes from z=0 (later y=0 after rotation) to stave length * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later @@ -270,39 +308,15 @@ void FT3Module::addStaveVolume( * Note also that we first need to check if the length is allowed given the inner * and outer radius of the layer. */ - double x_left = x_mid - Constants::single_sensor_width; - double x_right = x_mid + Constants::single_sensor_width; - std::pair absAllowedYRange = - calculate_y_range(x_left, x_right, Rin, Rout); - - // shift allowed range by tolerance. Note that this sum can be negative, but - // that is not a problem, it just means that we can always place staves over the edge - absAllowedYRange.first += tolerance_inner; - absAllowedYRange.second -= tolerance_outer; - - double maxStaveLength = absAllowedYRange.second - absAllowedYRange.first; - double staveLengthToUse = std::min(staveLength, maxStaveLength); - if (staveLengthToUse <= 0) { - LOG(warning) << "Stave " << volumeName << " has non-positive length after applying " - << "tolerance, skipping stave. Max allowed length: " << maxStaveLength - << " tolerance inner: " << tolerance_inner - << " tolerance outer: " << tolerance_outer; - return; - } - double staveLengthDiff = staveLength - staveLengthToUse; - if (staveLengthDiff > 0) { // had to cut it, hence y_mid must move too - y_mid = absAllowedYRange.first + staveLengthToUse / 2; - } - TGeoXtru* staveFull = new TGeoXtru(2); staveFull->SetName(( volumeName + "_Xtru_outer").c_str()); - staveFull->DefinePolygon(3, xv_outer, zv_outer); + staveFull->DefinePolygon(3, staveTriangles[0].data(), staveTriangles[1].data()); staveFull->DefineSection(0, 0); staveFull->DefineSection(1, staveLengthToUse); TGeoXtru* staveInner = new TGeoXtru(2); staveInner->SetName(( volumeName + "_Xtru_inner").c_str()); - staveInner->DefinePolygon(3, xv_inner, zv_inner); + staveInner->DefinePolygon(3, staveTriangles[2].data(), staveTriangles[3].data()); staveInner->DefineSection(0, 0); staveInner->DefineSection(1, staveLengthToUse); @@ -323,14 +337,29 @@ void FT3Module::addStaveVolume( * We still need to shift it in z to get the right staggered layout. * This means moving the staves that must be shifted in the opposite * direction they are facing: up for direction 1, and down for direction 0. + * + * Unlike a regular node placement, we have to put the stave at its + * starting point in y, not the midpoint. Hence, if we have the mirror, + * the starting point is the upper y value, since that is the bottom + * of the mirrored stave -- by the outer radius */ double z_shift = (direction == 1) ? z_stave_shift_abs : -z_stave_shift_abs; TGeoCombiTrans* combiTrans = - new TGeoCombiTrans(x_mid, y_mid - staveLengthToUse / 2, z_shift, rot); + new TGeoCombiTrans(x_mid, y_lower, z_shift, rot); motherVolume->AddNode(staveVolume, *volume_count, combiTrans); (*volume_count)++; + + // if the stave needs to be split, reuse the same volume on opposite side + if (splitStave) { + TGeoCombiTrans* combiTransSplit = + new TGeoCombiTrans(x_mid, -y_upper, z_shift, rot); + motherVolume->AddNode(staveVolume, + *volume_count, + combiTransSplit); + (*volume_count)++; + } } /* @@ -520,6 +549,8 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction // initialise all y_positions, vector over all staves/columns std::vector y_positionsPosNeg; unsigned volume_count = 0; // give each subvolume a unique ID + // stave triangle cross sections are the same for every stave (direction based) + std::array, 4> staveTriangles = buildStaveTriangle(direction); // Create the stave volumes and fill the y positions where to put sensors on the stave for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); @@ -552,18 +583,6 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction tolerance_outer = 0.; } - // Get whether the stave is shifted backward or not - double z_stave_shift_abs = Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; - // now create the stave volume - std::string stave_volume_name = - "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + - "_" + std::to_string(direction); - addStaveVolume( - motherVolume, stave_volume_name, direction, &volume_count, - Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, - Rin, Rout, Constants::x_midpoints[i_stave], - y_midpoint, mZ + z_stave_shift_abs - ); /* * There are three cases in which we want to mirror the stave around the x-axis, * which correspond to the stave not going fully from + to - Rout in y. @@ -575,28 +594,51 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction */ double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; double x_right = x_left + Constants::sensor2x1_width; - bool mirrorStaveAroundXAxis = false; - if (tolerance_inner >= 0) { - double x_innermost = std::min(std::abs(x_left), std::abs(x_right)); - mirrorStaveAroundXAxis = (x_innermost < Rin); - } else { // Have negative tolerance, so can place staves as wished - mirrorStaveAroundXAxis = (y_midpoint > Rin); + std::pair absAllowedYRange = + calculate_y_range(x_left, x_right, Rin, Rout); + + /* + * Shift allowed range by tolerance. Note that both values in the range must + * be non-negative, and if the inner is not, then set it to 0. This just means + * that there is no lower limit. The upper limit must however be larger than 0, + * if it is not, then skip this stave and give a warning. + */ + absAllowedYRange.first += tolerance_inner; + absAllowedYRange.second -= tolerance_outer; + + if (absAllowedYRange.first < 0) { + absAllowedYRange.first = 0; } + if (absAllowedYRange.second <= 0) { + LOG(warning) << "For stave " << i_stave << " in layer " << layerNumber + << " with direction " << direction << ": no space to place sensors after applying tolerances, skipping stave."; + continue; + } + + // Get whether the stave is shifted backward or not before creating + double z_stave_shift_abs = Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; + std::string stave_volume_name = + "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + "_" + std::to_string(direction); + addStaveVolume( + motherVolume, stave_volume_name, direction, &volume_count, + Constants::y_lengths[i_stave], staveTriangles, absAllowedYRange, + Constants::x_midpoints[i_stave], y_midpoint, mZ + z_stave_shift_abs + ); // Now create the mirrored stave - if (mirrorStaveAroundXAxis) { + if (mirrorStaveAroundX) { addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, - Constants::y_lengths[i_stave], tolerance_inner, tolerance_outer, - Rin, Rout, Constants::x_midpoints[i_stave], - -y_midpoint, mZ + z_stave_shift_abs + Constants::y_lengths[i_stave], staveTriangles, absAllowedYRange, + Constants::x_midpoints[i_stave], -y_midpoint, mZ + z_stave_shift_abs ); } // now add the sensor positions on the stave for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) { fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left, - Constants::kSensorsPerStack[i_kSens], tolerance_inner, - tolerance_outer, y_ranges); + Constants::kSensorsPerStack[i_kSens], y_ranges, + absAllowedYRange); } } From 9a93b25fedb613ad7d4b317ef3f002b717aeb3bb Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Mon, 13 Apr 2026 14:46:33 +0200 Subject: [PATCH 21/30] Add support to place either a stack gap or single sensor around y=0 in staves that have full +Rout to -Rout coverage --- .../FT3/base/include/FT3Base/FT3BaseParam.h | 3 +++ .../ALICE3/FT3/simulation/src/FT3Module.cxx | 22 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 3962860038d8d..0531be8649f7d 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -43,6 +43,9 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { bool cutStavesOnNominalRadius_inner = false; bool cutStavesOnNominalRadius_outer = false; + // What to place over x=0 line in case of full outer-outer stave: Gap or Sensor + bool placeSensorInMiddleOfStave = false; + O2ParamDef(FT3BaseParam, "FT3Base"); }; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 9fa0e337980dc..c61aeb496ee24 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -560,9 +560,25 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction bool mirrorStaveAroundX = false; // default positive and negative starting points has a gap around x-axis for symmetry double stave_half_length = Constants::y_lengths[i_stave] / 2; - PositionRangeType y_ranges = - {{Constants::stackGap / 2, stave_half_length}, - {-Constants::stackGap / 2, -stave_half_length}}; + PositionRangeType y_ranges; + if (ft3Params.placeSensorInMiddleOfStave) { + /* + * We want a sensor to cross over the x-axis for coverage at y=0 + * N.B. not necessarily exactly mirrored, only if stack gap is the same + * as the gap between sensors in a stack. + */ + y_ranges = {{-Constants::sensor2x1_height / 2, + stave_half_length}, + {-Constants::sensor2x1_height / 2 - Constants::stackGap, + -stave_half_length}}; + } else { + /* + * Otherwise have a gap around y=0, so sensors are not placed there. + * This means the stave is perfectly mirrored around the x-axis. + */ + y_ranges = {{Constants::stackGap / 2, stave_half_length}, + {-Constants::stackGap / 2, -stave_half_length}}; + } auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave, use this for starting points From 95d7c9bc4ffe27acb513050e74e32053746a5078 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 14 Apr 2026 14:36:53 +0200 Subject: [PATCH 22/30] Bugfix: When starting sensor placement around the x-axis you can have a different number of sensor stacks on the positive/negative side of the stave leading to garbage memory access. Fixed now. --- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index c61aeb496ee24..46be5c71174c0 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -166,11 +166,11 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double if (!y_positions.first.empty()) { // sensors already placed double previousStackHeight = Constants::getStackHeight(y_positions.first.back().second); y_top = y_positions.first.back().first + previousStackHeight + Constants::stackGap; - } else if (y_ranges.first.first < absAllowedYRange.first) { - // given starting y is lower than the minimum allowed y, start at the latter. - y_top = absAllowedYRange.first; + } else if (absAllowedYRange.first > 0) { + // there is a minimum inner value --> start at the max of the two + y_top = std::max(absAllowedYRange.first, y_ranges.first.first); } else { - // given starting y is acceptable, start there + // No inner minimum value, start at given value y_top = y_ranges.first.first; } // fill positive y sensor positions @@ -186,15 +186,18 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double // subtract instead to move further down double previousStackHeight = Constants::getStackHeight(y_positions.second.back().second); y_bottom = y_positions.second.back().first - previousStackHeight - Constants::stackGap; - } else if (y_ranges.second.first > -absAllowedYRange.first) { - // given starting y is closer to x-axis than max allowed y, start at the latter. - y_bottom = -absAllowedYRange.first; + } else if (absAllowedYRange.first > 0) { + // there is a minimum inner value --> start at the min of the two + y_bottom = std::min(-absAllowedYRange.first, y_ranges.second.first); } else { - // given starting y is acceptable, start there + // No inner minimum value, start at given value y_bottom = y_ranges.second.first; } // fill in the sensors on negative y while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) { + LOG(info) << "\t\tPlacing " << kSensorStack << " sensors at y = " << y_bottom + << " to " << (y_bottom - sensorStackHeight) + << " with x_left = " << x_left; y_positions.second.emplace_back(y_bottom, kSensorStack); y_bottom -= sensorAbsStackYShift; } @@ -658,6 +661,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } } + // Create volumes for the sensors and the support materials on top of the stave for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { double x_mid = Constants::x_midpoints[i_stave]; int staveID = Constants::staveIdxToID(i_stave); @@ -686,12 +690,12 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction : -Constants::z_offsetStave; } - for (unsigned i_y_pos = 0; i_y_pos < y_positionsPosNeg[i_stave].first.size(); i_y_pos++) { - for (int y_sign = -1; y_sign < 2; y_sign+=2) { - // place sensors at positive and negative y - const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first - : y_positionsPosNeg[i_stave].second; - // define starting midpoint: y = y_start +- distance to middle of sensor + for (int y_sign = -1; y_sign < 2; y_sign+=2) { + // place sensors at positive and negative y + const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first + : y_positionsPosNeg[i_stave].second; + // define starting midpoint: y = y_start +- distance to middle of sensor + for (unsigned i_y_pos = 0; i_y_pos < positions.size(); i_y_pos++) { double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2; for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) { TGeoVolume* sensor; From 94251b3bdead5d8062a92fa39c35f80b4b1815f1 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 14 Apr 2026 18:16:28 +0200 Subject: [PATCH 23/30] Add option to draw reference circles onto the layer -- strictly for visualisation purposes --- .../FT3/base/include/FT3Base/FT3BaseParam.h | 3 ++ .../include/FT3Simulation/FT3Layer.h | 1 + .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 37 +++++++++++++++++-- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 9 +++-- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 0531be8649f7d..712d67c4e9900 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -46,6 +46,9 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // What to place over x=0 line in case of full outer-outer stave: Gap or Sensor bool placeSensorInMiddleOfStave = false; + // Draw reference circles at inner and outer radius of the layer, for visualisation + bool drawReferenceCircles = false; + O2ParamDef(FT3BaseParam, "FT3Base"); }; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h index f6acebe80ac33..282f8fd274ec0 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h @@ -64,6 +64,7 @@ class FT3Layer : public TObject // create layer for disk support void createSeparationLayer(TGeoVolume* motherVolume, const std::string& separationLayerName); void createSeparationLayer_waterCooling(TGeoVolume* motherVolume, const std::string& separationLayerName); + void createReferenceCircles(TGeoVolume* motherVolume, const std::string& name); static TGeoMaterial* carbonFiberMat; static TGeoMedium* medCarbonFiber; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index d3db1660adc90..cafa21a90e65c 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -226,6 +226,24 @@ void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string motherVolume->AddNode(carbonFiberLayerVol2, 1, new TGeoTranslation(0, 0, 0 + zSeparation)); } +void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::string& name) { + + // create reference circles at the inner and outer radius of the layer, for visualization purposes + TGeoTube* innerCircle = new TGeoTube(mInnerRadius - 0.1, mInnerRadius + 0.1, 0.01); + TGeoTube* outerCircle = new TGeoTube(mOuterRadius - 0.1, mOuterRadius + 0.1, 0.01); + + TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, gGeoManager->GetMedium("FT3_AIR$")); + TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, gGeoManager->GetMedium("FT3_AIR$")); + + innerCircleVol->SetLineColor(kRed); + outerCircleVol->SetLineColor(kBlue); + + double z_position = mDirection ? 0.5 : -0.5; + + motherVolume->AddNode(innerCircleVol, 1, new TGeoTranslation(0, 0, z_position)); + motherVolume->AddNode(outerCircleVol, 1, new TGeoTranslation(0, 0, z_position)); +} + void FT3Layer::createLayer(TGeoVolume* motherVolume) { auto& ft3Params = FT3BaseParam::Instance(); @@ -393,16 +411,27 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); // Add a little additional room in radius; Try with 1.5 cm thickness - TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - layerVol->SetLineColor(kYellow + 2); - + TGeoVolume* layerVol = nullptr; if (ft3Params.layoutFT3 == kSegmented) { + // Add a little additional room in radius + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow + 2); // createSeparationLayer_waterCooling(motherVolume, separationLayerName); createSeparationLayer(layerVol, separationLayerName); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); } else if (ft3Params.layoutFT3 == kSegmentedStave) { + // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) + // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at + // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 6); + 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); + createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes + } module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., layerVol); } diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 46be5c71174c0..6b2f7698904d6 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -195,9 +195,6 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double } // fill in the sensors on negative y while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) { - LOG(info) << "\t\tPlacing " << kSensorStack << " sensors at y = " << y_bottom - << " to " << (y_bottom - sensorStackHeight) - << " with x_left = " << x_left; y_positions.second.emplace_back(y_bottom, kSensorStack); y_bottom -= sensorAbsStackYShift; } @@ -353,6 +350,9 @@ void FT3Module::addStaveVolume( *volume_count, combiTrans); (*volume_count)++; + LOG(info) << "\t\tPlacing stave at x = " << x_mid + << ", y = " << y_lower << " to " << y_upper + << ", z shift = " << z_shift; // if the stave needs to be split, reuse the same volume on opposite side if (splitStave) { @@ -362,6 +362,9 @@ void FT3Module::addStaveVolume( *volume_count, combiTransSplit); (*volume_count)++; + LOG(info) << "\t\tPlacing split stave at x = " << x_mid + << ", y = " << -y_upper << " to " << -y_lower + << ", z shift = " << z_shift; } } From 6871f81120484e114db7e327bf22d1da018215ba Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 14 Apr 2026 18:16:57 +0200 Subject: [PATCH 24/30] remove stale info statements --- Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 6b2f7698904d6..9cbb29f37d065 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -350,9 +350,6 @@ void FT3Module::addStaveVolume( *volume_count, combiTrans); (*volume_count)++; - LOG(info) << "\t\tPlacing stave at x = " << x_mid - << ", y = " << y_lower << " to " << y_upper - << ", z shift = " << z_shift; // if the stave needs to be split, reuse the same volume on opposite side if (splitStave) { @@ -362,9 +359,6 @@ void FT3Module::addStaveVolume( *volume_count, combiTransSplit); (*volume_count)++; - LOG(info) << "\t\tPlacing split stave at x = " << x_mid - << ", y = " << -y_upper << " to " << -y_lower - << ", z shift = " << z_shift; } } From 6bb8039b32cd60a01fabbc80db70c0a55ea09aea Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Wed, 15 Apr 2026 16:06:12 +0200 Subject: [PATCH 25/30] Remove stale overlap argument, and add local offset in z. In contrast to previous layout, to encapsulate the staves with face at local z=0 in air, we need to shift the staves and sensors locally since the mother volume is always around local z=0. Shift the layer volume by the same amount when adding it to get the right global position. --- .../include/FT3Simulation/FT3Module.h | 22 ++--- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 19 +++- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 86 +++++++++++-------- 3 files changed, 79 insertions(+), 48 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 096f432efaa4f..50949325e8420 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -56,7 +56,7 @@ class FT3Module void createModule_staveGeo( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, TGeoVolume* motherVolume); + double Rout, double z_offset_local, TGeoVolume* motherVolume); private: static void create_layout( @@ -66,7 +66,7 @@ class FT3Module void create_layout_staveGeo( double mZ, int layerNumber, int direction, double Rin, - double Rout, double overlap, TGeoVolume* motherVolume); + double Rout, double z_offset_local, TGeoVolume* motherVolume); // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, @@ -77,28 +77,28 @@ class FT3Module unsigned* volume_count, double staveLength, std::array, 4> staveTriangles, std::pair& absAllowedYRange, - double x_mid, double y_mid, double z_stave_shift_abs); + double x_mid, double y_mid, double z_stave_shift_forward); void addDetectorVolume( TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, double x_mid, double y_mid, double z_mid, double x_half_length, double y_half_length, double z_half_length); void add2x1GlueVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double x_mid, double y_mid, double z_mid, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, std::string element_glued_to); void add2x1CopperVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double x_mid, double y_mid, double z_mid); + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid); void add2x1KaptonVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double x_mid, double y_mid, double z_mid); + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid); void addSingleSensorVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double active_x_mid, double y_mid, double z_mid, bool isLeft); + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, bool isLeft); }; #endif // FT3MODULE_H diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index cafa21a90e65c..99bcc60773203 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -17,6 +17,7 @@ #include "FT3Simulation/FT3Layer.h" #include "FT3Base/GeometryTGeo.h" #include "FT3Base/FT3BaseParam.h" +#include "FT3Simulation/FT3ModuleConstants.h" #include // for TGeoManager, gGeoManager #include // for TGeoCombiTrans, TGeoRotation, etc @@ -412,6 +413,8 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); TGeoVolume* layerVol = nullptr; + // shift stave volumes into layer volume, since nominal z_{stave face} = 0 + double z_local_offset = 0; if (ft3Params.layoutFT3 == kSegmented) { // Add a little additional room in radius TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); @@ -425,19 +428,29 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 6); + double z_layer_thickness = // need to shift internally with this + o2::ft3::ModuleConstants::staveTriangleHeight + + o2::ft3::ModuleConstants::z_offsetStave + + o2::ft3::ModuleConstants::siliconThickness + + o2::ft3::ModuleConstants::copperThickness + + o2::ft3::ModuleConstants::kaptonThickness + + o2::ft3::ModuleConstants::epoxyThickness * 2 + + 0.5; // add some extra room to ensure all volumes are encapsulated + z_local_offset = z_layer_thickness / 2.0; + TGeoTube* layer = new TGeoTube(mInnerRadius - 12, mOuterRadius + 5, z_layer_thickness / 2); 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); createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes } + // need the -0.5 added to local offset to ensure all sensor modules are inside the layer module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, 0., layerVol); + mOuterRadius, z_local_offset, layerVol); } // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); - auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ, FwdDiskRotation); + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + z_local_offset, FwdDiskRotation); LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 9cbb29f37d065..1a58cb5354163 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -267,7 +267,7 @@ void FT3Module::addStaveVolume( unsigned* volume_count, double staveLength, std::array, 4> staveTriangles, std::pair& absAllowedYRange, - double x_mid, double y_mid, double z_stave_shift_abs) + double x_mid, double y_mid, double z_stave_shift_forward) { // The allowed y range is assumed to be non-negative. if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 || @@ -343,7 +343,7 @@ void FT3Module::addStaveVolume( * the starting point is the upper y value, since that is the bottom * of the mirrored stave -- by the outer radius */ - double z_shift = (direction == 1) ? z_stave_shift_abs : -z_stave_shift_abs; + double z_shift = (direction == 1) ? z_stave_shift_forward : -z_stave_shift_forward; TGeoCombiTrans* combiTrans = new TGeoCombiTrans(x_mid, y_lower, z_shift, rot); motherVolume->AddNode(staveVolume, @@ -368,8 +368,8 @@ void FT3Module::addStaveVolume( */ void FT3Module::addDetectorVolume( - TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, - double x_mid, double y_mid, double z_mid, + TGeoVolume* motherVolume, std::string volumeName, int color, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, double x_half_length, double y_half_length, double z_half_length) { TGeoManager* geoManager = gGeoManager; @@ -394,13 +394,15 @@ void FT3Module::addDetectorVolume( * immediately for a whole 2x1 layout, under both the active and inactive region. */ void FT3Module::add2x1GlueVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double x_mid, double y_mid, double z_mid, + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid, std::string element_glued_to) { std::string glue_name = "FT3glue_" + element_glued_to + "_" - + std::to_string(layerNumber) + "_" + std::to_string(direction) - + "_" + std::to_string(*volume_count); + + std::to_string(layerNumber) + "_" + + std::to_string(direction) + "_" + + std::to_string(stave_idx) + "_" + + std::to_string(*volume_count); addDetectorVolume( motherVolume, glue_name, Constants::glueColor, volume_count, x_mid, y_mid, z_mid, @@ -413,11 +415,13 @@ void FT3Module::add2x1GlueVolume( * As with the glue, this is a whole 2x1 layout volume. */ void FT3Module::add2x1CopperVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double x_mid, double y_mid, double z_mid) + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid) { std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*volume_count); + + std::to_string(direction) + "_" + + std::to_string(stave_idx) + "_" + + std::to_string(*volume_count); addDetectorVolume( motherVolume, copper_name, Constants::CuColor, volume_count, x_mid, y_mid, z_mid, @@ -430,11 +434,13 @@ void FT3Module::add2x1CopperVolume( * As with copper and glue, this is a whole 2x1 layout volume. */ void FT3Module::add2x1KaptonVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double x_mid, double y_mid, double z_mid) + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid) { std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*volume_count); + + std::to_string(direction) + "_" + + std::to_string(stave_idx) + "_" + + std::to_string(*volume_count); addDetectorVolume( motherVolume, kapton_name, Constants::kaptonColor, volume_count, x_mid, y_mid, z_mid, @@ -461,14 +467,17 @@ void FT3Module::add2x1KaptonVolume( * isLeft: whether the sensor is on the left or right in the 2x1 layout */ void FT3Module::addSingleSensorVolume( - TGeoVolume* motherVolume, int layerNumber, int direction, unsigned* volume_count, - double active_x_mid, double y_mid, double z_mid, bool isLeft) + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double active_x_mid, double y_mid, double z_mid, + bool isLeft) { TGeoVolume* sensor; TGeoManager* geoManager = gGeoManager; // ACTIVE AREA std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*volume_count); + + std::to_string(direction) + "_" + + std::to_string(stave_idx) + "_" + + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); sensor->SetLineColor(Constants::SiColor); @@ -488,7 +497,9 @@ void FT3Module::addSingleSensorVolume( : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); std::string sensor_inactive_name = "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" + std::to_string(*volume_count); + + std::to_string(direction) + "_" + + std::to_string(stave_idx) + "_" + + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); sensor->SetLineColor(Constants::SiInactiveColor); @@ -506,8 +517,8 @@ void FT3Module::addSingleSensorVolume( } void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, - double Rin, double Rout, double overlap, - TGeoVolume* motherVolume) + double Rin, double Rout, double z_offset_local, + TGeoVolume* motherVolume) { LOG(debug) << "FT3Module: create_layout_staveGeo - Layer " << layerNumber << ", Direction " << direction; @@ -527,9 +538,13 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction * Naturally, this will be mirrored for layers in the backwards direction, * such that the face of the sensors always face the interaction region. * - * Currently, we stipulate that the default stave face is at local z=0. + * Currently, we stipulate that the default stave face is at local z + * that is slightly shifted by the air thickness encapsulating the layer + * to avoid overlaps with the air and services. All offsets are + * calculated for backward direction (since that is a positive shift), + * and then flipped for forward. */ - double z_offset_to_carbon_face = 0; + double z_offset_to_carbon_face = z_offset_local; double z_offset_to_glue_Ka = z_offset_to_carbon_face + Constants::epoxyThickness / 2; double z_offset_to_kapton = @@ -632,21 +647,24 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } // Get whether the stave is shifted backward or not before creating - double z_stave_shift_abs = Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; + double z_stave_shift_abs = + Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; + double z_stave_shift_forward = // move staves more inward to fit in layer volume + -z_offset_to_carbon_face + z_stave_shift_abs; std::string stave_volume_name = "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction); addStaveVolume( motherVolume, stave_volume_name, direction, &volume_count, Constants::y_lengths[i_stave], staveTriangles, absAllowedYRange, - Constants::x_midpoints[i_stave], y_midpoint, mZ + z_stave_shift_abs + Constants::x_midpoints[i_stave], y_midpoint, z_stave_shift_forward ); // Now create the mirrored stave if (mirrorStaveAroundX) { addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, Constants::y_lengths[i_stave], staveTriangles, absAllowedYRange, - Constants::x_midpoints[i_stave], -y_midpoint, mZ + z_stave_shift_abs + Constants::x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward ); } @@ -700,36 +718,36 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction // left single sensor of the 2x1 double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; addSingleSensorVolume( - motherVolume, layerNumber, direction, &volume_count, + motherVolume, layerNumber, direction, i_stave, &volume_count, x_mid - Constants::active_width / 2, y_mid, z_mid, true ); // right single sensor of the 2x1 addSingleSensorVolume( - motherVolume, layerNumber, direction, &volume_count, + motherVolume, layerNumber, direction, i_stave, &volume_count, x_mid + Constants::active_width / 2, y_mid, z_mid, false ); // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( - motherVolume, layerNumber, direction, &volume_count, + motherVolume, layerNumber, direction, i_stave, &volume_count, x_mid, y_mid, z_mid, "SiCu" ); // ------------ (3) Copper layer (FPC) ------------ z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift; add2x1CopperVolume( - motherVolume, layerNumber, direction, &volume_count, + motherVolume, layerNumber, direction, i_stave, &volume_count, x_mid, y_mid, z_mid ); // ------------ (4) Kapton layer (FPC) ------------ z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift; add2x1KaptonVolume( - motherVolume, layerNumber, direction, &volume_count, + motherVolume, layerNumber, direction, i_stave, &volume_count, x_mid, y_mid, z_mid ); // ------------ (5) Epoxy glue layer between stave and Kapton ------------ z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( - motherVolume, layerNumber, direction, &volume_count, + motherVolume, layerNumber, direction, i_stave, &volume_count, x_mid, y_mid, z_mid, "CarbonKapton" ); // increment to next sensor: (height + gap of one sensor) @@ -1402,11 +1420,11 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R } void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction, - double Rin, double Rout, double overlap, - TGeoVolume* motherVolume) { + double Rin, double Rout, double z_offset_local, + TGeoVolume* motherVolume) { LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber << " at z=" << mZ << ", Direction " << direction; create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout, - overlap, motherVolume); + z_offset_local, motherVolume); LOG(debug) << "FT3Module: done createModule_staveGeo"; } From dd38ee6b7f70da7921ed45a6335cf78b814a6c57 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Wed, 15 Apr 2026 17:38:30 +0200 Subject: [PATCH 26/30] Fix placement issue to get sensor materials inside the volume. This now means that the stave faces are not at local z=0 but instead at z=+-totalSensorMaterialThickness+0.1. Also fix global position bug since movement outwards is directional. --- .../Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx | 4 +++- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 99bcc60773203..51cf259a5fac8 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -450,7 +450,9 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) } // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); - auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + z_local_offset, FwdDiskRotation); + // need to shift outwards always, so + forwards and - backwards + double z_offset_directional = mDirection ? z_local_offset : -z_local_offset; + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + z_offset_directional, FwdDiskRotation); LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 1a58cb5354163..bc332a6d07c8a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -538,13 +538,18 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction * Naturally, this will be mirrored for layers in the backwards direction, * such that the face of the sensors always face the interaction region. * - * Currently, we stipulate that the default stave face is at local z - * that is slightly shifted by the air thickness encapsulating the layer + * Currently, we stipulate that the default stave face is at local z=0, + * that is then shifted by the half air thickness encapsulating the layer * to avoid overlaps with the air and services. All offsets are * calculated for backward direction (since that is a positive shift), - * and then flipped for forward. + * and then flipped for forward. At that point, the innermost/frontmost + * stave face is at the edge of the air volume, so we shift it back a little + * to make space for the sensor materials and a slight margin. */ - double z_offset_to_carbon_face = z_offset_local; + double totalSensorMaterialThickness = + Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness; + double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1; double z_offset_to_glue_Ka = z_offset_to_carbon_face + Constants::epoxyThickness / 2; double z_offset_to_kapton = From 51182651117398dffc9bf190267034d1b5f16f57 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 28 Apr 2026 18:55:08 +0200 Subject: [PATCH 27/30] Add OT only segmentation and change defaults --- .../Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 712d67c4e9900..2a7cafe29544a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -25,6 +25,7 @@ enum eFT3Layout { kTrapezoidal, kSegmented, kSegmentedStave, + kSegmentedStaveOTOnly }; struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters @@ -40,13 +41,13 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { Float_t Layerx2X0 = 0.01; // override values from FT3ModuleConstants, inner and outer - bool cutStavesOnNominalRadius_inner = false; + bool cutStavesOnNominalRadius_inner = true; bool cutStavesOnNominalRadius_outer = false; // What to place over x=0 line in case of full outer-outer stave: Gap or Sensor bool placeSensorInMiddleOfStave = false; - // Draw reference circles at inner and outer radius of the layer, for visualisation + // Draw reference circles at inner and outer radius of stave layer, for visualisation bool drawReferenceCircles = false; O2ParamDef(FT3BaseParam, "FT3Base"); From 8f131f87856c5964ecb39e0faaf083d0088321f3 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 28 Apr 2026 18:56:33 +0200 Subject: [PATCH 28/30] Make stave geometry available with middle layer disks as well. Currently use simple calculated values for stave placements, these are subject to change. Hence the existence of kSegmentedOTOnly --- .../include/FT3Simulation/FT3Module.h | 6 +- .../FT3Simulation/FT3ModuleConstants.h | 153 ++++++++++++------ .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 92 +++++++---- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 44 ++--- 4 files changed, 191 insertions(+), 104 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 50949325e8420..5b04eeef4cb12 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -56,7 +56,8 @@ class FT3Module void createModule_staveGeo( double mZ, int layerNumber, int direction, double Rin, - double Rout, double z_offset_local, TGeoVolume* motherVolume); + double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume); private: static void create_layout( @@ -66,7 +67,8 @@ class FT3Module void create_layout_staveGeo( double mZ, int layerNumber, int direction, double Rin, - double Rout, double z_offset_local, TGeoVolume* motherVolume); + double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume); // Helper functions void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index 4b38441775355..cb074b44d36ba 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -59,52 +59,11 @@ namespace o2::ft3::ModuleConstants return nSensorsPerStack * sensor2x1_height + (nSensorsPerStack - 1) * sensor2x1_gap; } - /* - * Constants for staves are written for both positive - * and negative x even though they are just mirrored now, - * because there might be design changes in the future - * that require a non-mirrored layout, making it easier to - * change here if so required, even though it looks uglier now. - * - * The second element in the mapping pair is whether the stave - * with a certain ID should be mirrored around the x-axis. - */ - // map from Stave ID (1-indexed from other documents) to midpoint - // Do NOT add any zero midpoints, this is taken off separately - const std::map> staveID_to_y_midpoint = { - {-2, {39.0, true}}, - {-1, {41.4, true}}, - {1, {41.4, true}}, - {2, {39.0, true}} - }; - // lengths of staves, their midpoint, and their face - const std::vector y_lengths = { - 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, - 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, - 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, - 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8 - }; - const std::vector x_midpoints = { - -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L - -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L - 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R - 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R - }; - const double x_midpoint_spacing = 4.5; // assume constant for now - // which side of the disc do we place the stave? - // used for kSegmentedMarch26 for front/back face, and for - // kSegmentedStave for staggering staves in z (see z_offsetStave) - // accessed via stave index, NOT stave ID - const std::vector staveOnFront = - { - 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L - 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R - }; // small helper function to get 1-indexed stave ID, counting from the middle outwards, // with negative IDs on the left and positive IDs on the right - inline const int staveIdxToID(int staveIdx) { - unsigned nStavesOneSide = y_lengths.size() / 2; + inline const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc) { + unsigned nStavesOneSide = nStavesPerDisc / 2; bool isRight = staveIdx >= nStavesOneSide; return staveIdx - nStavesOneSide + isRight; } @@ -132,8 +91,10 @@ namespace o2::ft3::ModuleConstants */ // If midpoint spacing becomes non constant, this becomes a function // TODO: add some tolerance to avoid overlaps? - const double z_offsetStave = - staveTriangleHeight * (2 - x_midpoint_spacing / ( sensor2x1_width / 2 + staveSensorGap ) ); + inline const double z_offsetStave(double x_midpoint_spacing) { + return staveTriangleHeight * + (2 - x_midpoint_spacing / ( sensor2x1_width / 2 + staveSensorGap ) ); + } const int SiColor = kGreen; const int SiInactiveColor = kRed; @@ -141,6 +102,106 @@ namespace o2::ft3::ModuleConstants const int CuColor = kOrange; const int kaptonColor = kYellow; const int carbonColor = kBlack; -} + + // Struct for stave position configuration (varies between IT/OT) + struct StaveConfig { + /* + * Constants for staves are written for both positive + * and negative x even though they are just mirrored now, + * because there might be design changes in the future + * that require a non-mirrored layout, making it easier to + * change here if so required, even though it looks uglier now. + * + * The second element in the mapping pair is whether the stave + * with a certain ID should be mirrored around the x-axis. + */ + // map from Stave ID (1-indexed from other documents) to midpoint + // Do NOT add any zero midpoints, this is taken off separately + const std::map>& staveID_to_y_midpoint; + // lengths of staves, their midpoint, and their face + const std::vector& y_lengths; + const std::vector& x_midpoints; + double x_midpoint_spacing; + // which side of the disc do we place the stave? + // kSegmentedStave: staggering staves in z (see z_offsetStave) + // accessed via stave index, NOT stave ID + const std::vector& staveOnFront; + }; + + namespace OT_StavePositions { + const std::map> staveID_to_y_midpoint = { + {-2, {39.0, true}}, + {-1, {41.4, true}}, + {1, {41.4, true}}, + {2, {39.0, true}} + }; + const std::vector y_lengths = { + 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, + 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, + 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, + 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8 + }; + const std::vector x_midpoints = { + -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R + 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R + }; + const double x_midpoint_spacing = 4.5; // assume constant for now + const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R + }; + } // namespace OT_StavePositions + + namespace ML_StavePositions { + // Use prelim numbers for now, these will change! TODO + const std::map> staveID_to_y_midpoint = { + {-3, {19.1, true}}, + {-2, {21.8, true}}, + {-1, {22.5, true}}, + {1, {22.5, true}}, + {2, {21.8, true}}, + {3, {19.1, true}} + }; + const std::vector y_lengths = { + 30.5, 44.5, 53.6, 60.0, 64.6, 29.5, 25.8, 25.0, + 25.0, 25.8, 29.5, 64.6, 60.0, 53.6, 44.5, 30.5 + }; + const std::vector x_midpoints = { + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75 // R + }; + const double x_midpoint_spacing = 4.5; + const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, // L + 0, 1, 0, 1, 0, 1, 0, 1 // R + }; + } // namespace ML_StavePositions + + // Get stave configuration based on tracker type + inline StaveConfig getStaveConfig(bool isInnerDisk) { + if (isInnerDisk) { + return StaveConfig{ + ML_StavePositions::staveID_to_y_midpoint, + ML_StavePositions::y_lengths, + ML_StavePositions::x_midpoints, + ML_StavePositions::x_midpoint_spacing, + ML_StavePositions::staveOnFront + }; + } else { + return StaveConfig{ + OT_StavePositions::staveID_to_y_midpoint, + OT_StavePositions::y_lengths, + OT_StavePositions::x_midpoints, + OT_StavePositions::x_midpoint_spacing, + OT_StavePositions::staveOnFront + }; + } + } + +} // namespace o2::ft3::ModuleConstants #endif // FT3MODULECONSTANTS_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 51cf259a5fac8..2003e2556e868 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -403,7 +403,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); } else if (ft3Params.layoutFT3 == kSegmented || - ft3Params.layoutFT3 == kSegmentedStave) { + (ft3Params.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { FT3Module module; // layer structure @@ -413,42 +413,64 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); TGeoVolume* layerVol = nullptr; + // Add a little additional room in radius + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow + 2); + // createSeparationLayer_waterCooling(motherVolume, separationLayerName); + createSeparationLayer(layerVol, separationLayerName); + module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); + module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); + + // Finally put everything in the mother volume + auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); + // need to shift outwards always, so + forwards and - backwards + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + 0, FwdDiskRotation); + + LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); + motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else if (ft3Params.layoutFT3 == kSegmentedStave || + ft3Params.layoutFT3 == kSegmentedStaveOTOnly) { + FT3Module module; + + // layer structure + std::string frontLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; + std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; + std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); + + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoVolume* layerVol = nullptr; + + // set up stave config, differs between ML and OT disks + const Constants::StaveConfig& staveConfig = Constants::getStaveConfig(mIsMiddleLayer); + + // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) + // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at + // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs + double z_layer_thickness = // need to shift internally with this + o2::ft3::ModuleConstants::staveTriangleHeight + + o2::ft3::ModuleConstants::z_offsetStave(staveConfig.x_midpoint_spacing) + + o2::ft3::ModuleConstants::siliconThickness + + o2::ft3::ModuleConstants::copperThickness + + o2::ft3::ModuleConstants::kaptonThickness + + o2::ft3::ModuleConstants::epoxyThickness * 2 + + 0.5; // add some extra room to ensure all volumes are encapsulated + // shift stave volumes into layer volume, since nominal z_{stave face} = 0 - double z_local_offset = 0; - if (ft3Params.layoutFT3 == kSegmented) { - // Add a little additional room in radius - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); - layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - layerVol->SetLineColor(kYellow + 2); - // createSeparationLayer_waterCooling(motherVolume, separationLayerName); - createSeparationLayer(layerVol, separationLayerName); - module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); - module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); - } else if (ft3Params.layoutFT3 == kSegmentedStave) { - // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) - // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at - // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs - double z_layer_thickness = // need to shift internally with this - o2::ft3::ModuleConstants::staveTriangleHeight + - o2::ft3::ModuleConstants::z_offsetStave + - o2::ft3::ModuleConstants::siliconThickness + - o2::ft3::ModuleConstants::copperThickness + - o2::ft3::ModuleConstants::kaptonThickness + - o2::ft3::ModuleConstants::epoxyThickness * 2 + - 0.5; // add some extra room to ensure all volumes are encapsulated - z_local_offset = z_layer_thickness / 2.0; - TGeoTube* layer = new TGeoTube(mInnerRadius - 12, mOuterRadius + 5, z_layer_thickness / 2); - 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); - createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes - } - // need the -0.5 added to local offset to ensure all sensor modules are inside the layer - module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, z_local_offset, layerVol); + double z_local_offset = z_layer_thickness / 2.0; + TGeoTube* layer = new TGeoTube(mInnerRadius - 12, mOuterRadius + 5, z_layer_thickness / 2); + 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); + createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes } - // Finally put everything in the mother volume + + // need the -0.5 added to local offset to ensure all sensor modules are inside the layer + module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, z_local_offset, staveConfig, layerVol); + // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); // need to shift outwards always, so + forwards and - backwards double z_offset_directional = mDirection ? z_local_offset : -z_local_offset; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index bc332a6d07c8a..56db13bde5251 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -518,6 +518,7 @@ void FT3Module::addSingleSensorVolume( void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, TGeoVolume* motherVolume) { LOG(debug) << "FT3Module: create_layout_staveGeo - Layer " @@ -572,14 +573,14 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction // stave triangle cross sections are the same for every stave (direction based) std::array, 4> staveTriangles = buildStaveTriangle(direction); // Create the stave volumes and fill the y positions where to put sensors on the stave - for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); - const int staveID = Constants::staveIdxToID(i_stave); + const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); double y_midpoint = 0.; bool mirrorStaveAroundX = false; // default positive and negative starting points has a gap around x-axis for symmetry - double stave_half_length = Constants::y_lengths[i_stave] / 2; + double stave_half_length = staveConfig.y_lengths[i_stave] / 2; PositionRangeType y_ranges; if (ft3Params.placeSensorInMiddleOfStave) { /* @@ -599,8 +600,8 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction y_ranges = {{Constants::stackGap / 2, stave_half_length}, {-Constants::stackGap / 2, -stave_half_length}}; } - auto y_midpoint_it = Constants::staveID_to_y_midpoint.find(staveID); - if ( y_midpoint_it != Constants::staveID_to_y_midpoint.end() ) { + auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID); + if ( y_midpoint_it != staveConfig.staveID_to_y_midpoint.end() ) { // there is a defined midpoint for this stave, use this for starting points y_midpoint = y_midpoint_it->second.first; // avoid double map lookup mirrorStaveAroundX = y_midpoint_it->second.second; @@ -628,7 +629,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction * (2) The inner tolerance is large (allow stave placement as wished) * a) AND the given stave midpoint is above the inner radius */ - double x_left = Constants::x_midpoints[i_stave] - Constants::sensor2x1_width / 2; + double x_left = staveConfig.x_midpoints[i_stave] - Constants::sensor2x1_width / 2; double x_right = x_left + Constants::sensor2x1_width; std::pair absAllowedYRange = calculate_y_range(x_left, x_right, Rin, Rout); @@ -652,8 +653,8 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } // Get whether the stave is shifted backward or not before creating - double z_stave_shift_abs = - Constants::staveOnFront[i_stave] ? 0 : Constants::z_offsetStave; + double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? + 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); double z_stave_shift_forward = // move staves more inward to fit in layer volume -z_offset_to_carbon_face + z_stave_shift_abs; std::string stave_volume_name = @@ -661,15 +662,15 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction "_" + std::to_string(direction); addStaveVolume( motherVolume, stave_volume_name, direction, &volume_count, - Constants::y_lengths[i_stave], staveTriangles, absAllowedYRange, - Constants::x_midpoints[i_stave], y_midpoint, z_stave_shift_forward + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward ); // Now create the mirrored stave if (mirrorStaveAroundX) { addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, - Constants::y_lengths[i_stave], staveTriangles, absAllowedYRange, - Constants::x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward ); } @@ -682,9 +683,9 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } // Create volumes for the sensors and the support materials on top of the stave - for (unsigned i_stave = 0; i_stave < Constants::x_midpoints.size(); i_stave++) { - double x_mid = Constants::x_midpoints[i_stave]; - int staveID = Constants::staveIdxToID(i_stave); + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { + double x_mid = staveConfig.x_midpoints[i_stave]; + int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); /* * Declare an offset multiplier for the z offsets, used for distinguishing * sensors facing either forward or backward. @@ -696,18 +697,18 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction */ bool isFront; if (direction == 1) { // direction = 1 is forward - isFront = Constants::staveOnFront[i_stave]; + isFront = staveConfig.staveOnFront[i_stave]; } else { - isFront = !(Constants::staveOnFront[i_stave]); + isFront = !(staveConfig.staveOnFront[i_stave]); } int z_offset_multiplier = (direction == 1) ? -1 : 1; // Get whether the stave is shifted for staggering or not double z_stave_shift = 0; - if (!Constants::staveOnFront[i_stave]) { + if (!staveConfig.staveOnFront[i_stave]) { // in forward direction, shifting backwards means +z shift - z_stave_shift = (direction == 1) ? Constants::z_offsetStave - : -Constants::z_offsetStave; + z_stave_shift = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing) + : -Constants::z_offsetStave(staveConfig.x_midpoint_spacing); } for (int y_sign = -1; y_sign < 2; y_sign+=2) { @@ -1426,10 +1427,11 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, TGeoVolume* motherVolume) { LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber << " at z=" << mZ << ", Direction " << direction; create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout, - z_offset_local, motherVolume); + z_offset_local, staveConfig, motherVolume); LOG(debug) << "FT3Module: done createModule_staveGeo"; } From a33fde3d69b8c84c44f874f13730edb9d50d8f74 Mon Sep 17 00:00:00 2001 From: Justus Rudolph Date: Tue, 28 Apr 2026 18:58:47 +0200 Subject: [PATCH 29/30] Change default to stave segmentation for outer disks only --- .../Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 2a7cafe29544a..b0f26bc4675d4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -29,7 +29,7 @@ enum eFT3Layout { }; struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - eFT3Layout layoutFT3 = kSegmentedStave; + eFT3Layout layoutFT3 = kSegmentedStaveOTOnly; int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters From 0764ce06df664036e07e891e5eaf80fc2a347778 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Tue, 28 Apr 2026 17:01:39 +0000 Subject: [PATCH 30/30] Please consider the following formatting changes --- .../include/FT3Simulation/FT3Module.h | 6 +- .../FT3Simulation/FT3ModuleConstants.h | 353 +++++++++--------- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 16 +- .../ALICE3/FT3/simulation/src/FT3Module.cxx | 215 +++++------ 4 files changed, 277 insertions(+), 313 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h index 5b04eeef4cb12..1311c6a4ff1b5 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -84,16 +84,16 @@ class FT3Module TGeoVolume* motherVolume, std::string volumeName, int color, unsigned* volume_count, double x_mid, double y_mid, double z_mid, double x_half_length, double y_half_length, double z_half_length); - + void add2x1GlueVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, unsigned* volume_count, double x_mid, double y_mid, double z_mid, std::string element_glued_to); - + void add2x1CopperVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, unsigned* volume_count, double x_mid, double y_mid, double z_mid); - + void add2x1KaptonVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, unsigned* volume_count, double x_mid, double y_mid, double z_mid); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index cb074b44d36ba..1fe9f404245c1 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -22,186 +22,185 @@ namespace o2::ft3::ModuleConstants { - /* CURRENT STATUS: - * 25x32mm sensors, 2mm inactive on one side - * Most granular layout is 2x1 sensors, where the one on the right has the inactive region - * on the right, and the one on the left has the inactive region on the left. - * When stacking 2x1 modules, there is a 0.2mm gap between them. By default, we assume this - * gap to be ABOVE the most recently placed module. - * - * |<- 25mm ->|<- 25mm ->| - * _______________________ - * ----------------------- 0.2mm gap above - * | | | | | - * | | | | | - * | | | | | - * | | | | | 32mm sensor height - * | | | | | - * | | | | | - * ------------------------ - * - */ - // First set all layout constants for the rest of the function - const double single_sensor_width = 2.5; - const double single_sensor_height = 3.2; - const double inactive_width = 0.2; - const double sensor2x1_gap = 0.02; - const double stackGap = sensor2x1_gap; // gap between 2xN module stacks - - const double active_width = single_sensor_width - inactive_width; - const double active_height = single_sensor_height; - - const double sensor2x1_width = 2 * single_sensor_width; - const double sensor2x1_active_width = 2 * active_width; - const double sensor2x1_height = single_sensor_height; - const std::vector kSensorsPerStack = {4,2,1}; - inline const double getStackHeight(unsigned nSensorsPerStack) { - return nSensorsPerStack * sensor2x1_height + - (nSensorsPerStack - 1) * sensor2x1_gap; - } - - // small helper function to get 1-indexed stave ID, counting from the middle outwards, - // with negative IDs on the left and positive IDs on the right - inline const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc) { - unsigned nStavesOneSide = nStavesPerDisc / 2; - bool isRight = staveIdx >= nStavesOneSide; - return staveIdx - nStavesOneSide + isRight; - } +/* CURRENT STATUS: + * 25x32mm sensors, 2mm inactive on one side + * Most granular layout is 2x1 sensors, where the one on the right has the inactive region + * on the right, and the one on the left has the inactive region on the left. + * When stacking 2x1 modules, there is a 0.2mm gap between them. By default, we assume this + * gap to be ABOVE the most recently placed module. + * + * |<- 25mm ->|<- 25mm ->| + * _______________________ + * ----------------------- 0.2mm gap above + * | | | | | + * | | | | | + * | | | | | + * | | | | | 32mm sensor height + * | | | | | + * | | | | | + * ------------------------ + * + */ +// First set all layout constants for the rest of the function +const double single_sensor_width = 2.5; +const double single_sensor_height = 3.2; +const double inactive_width = 0.2; +const double sensor2x1_gap = 0.02; +const double stackGap = sensor2x1_gap; // gap between 2xN module stacks + +const double active_width = single_sensor_width - inactive_width; +const double active_height = single_sensor_height; + +const double sensor2x1_width = 2 * single_sensor_width; +const double sensor2x1_active_width = 2 * active_width; +const double sensor2x1_height = single_sensor_height; +const std::vector kSensorsPerStack = {4, 2, 1}; +inline const double getStackHeight(unsigned nSensorsPerStack) +{ + return nSensorsPerStack * sensor2x1_height + + (nSensorsPerStack - 1) * sensor2x1_gap; +} - // material properties - const double siliconThickness = 0.01; - const double copperThickness = 0.006; - const double kaptonThickness = 0.03; - const double epoxyThickness = 0.0012; - - const double effectiveCarbonThickness_Stave = 0.02; // foam + shell - const double staveOpeningAngle = 60 * TMath::DegToRad(); - const double sinTheta = TMath::Sin(staveOpeningAngle / 2); - const double alpha = TMath::Pi() / 2 - staveOpeningAngle / 2; // bottom angles - const double staveSensorGap = 0.1; // 2mm padding on each side when sensor is glued - const double staveTriangleHeight = (sensor2x1_width + 2 * staveSensorGap) / 2.0 - / tan(staveOpeningAngle / 2.0); - /* - * Now describe the offset of every other stave in z to avoid overlaps - * ______ ______ - * \ /______\ / | <-- z_offsetStave - * \ / \ / \ / - * \/ \ / \/ - * \/ +// small helper function to get 1-indexed stave ID, counting from the middle outwards, +// with negative IDs on the left and positive IDs on the right +inline const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc) +{ + unsigned nStavesOneSide = nStavesPerDisc / 2; + bool isRight = staveIdx >= nStavesOneSide; + return staveIdx - nStavesOneSide + isRight; +} + +// material properties +const double siliconThickness = 0.01; +const double copperThickness = 0.006; +const double kaptonThickness = 0.03; +const double epoxyThickness = 0.0012; + +const double effectiveCarbonThickness_Stave = 0.02; // foam + shell +const double staveOpeningAngle = 60 * TMath::DegToRad(); +const double sinTheta = TMath::Sin(staveOpeningAngle / 2); +const double alpha = TMath::Pi() / 2 - staveOpeningAngle / 2; // bottom angles +const double staveSensorGap = 0.1; // 2mm padding on each side when sensor is glued +const double staveTriangleHeight = (sensor2x1_width + 2 * staveSensorGap) / 2.0 / tan(staveOpeningAngle / 2.0); +/* + * Now describe the offset of every other stave in z to avoid overlaps + * ______ ______ + * \ /______\ / | <-- z_offsetStave + * \ / \ / \ / + * \/ \ / \/ + * \/ + */ +// If midpoint spacing becomes non constant, this becomes a function +// TODO: add some tolerance to avoid overlaps? +inline const double z_offsetStave(double x_midpoint_spacing) +{ + return staveTriangleHeight * + (2 - x_midpoint_spacing / (sensor2x1_width / 2 + staveSensorGap)); +} + +const int SiColor = kGreen; +const int SiInactiveColor = kRed; +const int glueColor = kBlue; +const int CuColor = kOrange; +const int kaptonColor = kYellow; +const int carbonColor = kBlack; + +// Struct for stave position configuration (varies between IT/OT) +struct StaveConfig { + /* + * Constants for staves are written for both positive + * and negative x even though they are just mirrored now, + * because there might be design changes in the future + * that require a non-mirrored layout, making it easier to + * change here if so required, even though it looks uglier now. + * + * The second element in the mapping pair is whether the stave + * with a certain ID should be mirrored around the x-axis. */ - // If midpoint spacing becomes non constant, this becomes a function - // TODO: add some tolerance to avoid overlaps? - inline const double z_offsetStave(double x_midpoint_spacing) { - return staveTriangleHeight * - (2 - x_midpoint_spacing / ( sensor2x1_width / 2 + staveSensorGap ) ); - } - - const int SiColor = kGreen; - const int SiInactiveColor = kRed; - const int glueColor = kBlue; - const int CuColor = kOrange; - const int kaptonColor = kYellow; - const int carbonColor = kBlack; - - // Struct for stave position configuration (varies between IT/OT) - struct StaveConfig { - /* - * Constants for staves are written for both positive - * and negative x even though they are just mirrored now, - * because there might be design changes in the future - * that require a non-mirrored layout, making it easier to - * change here if so required, even though it looks uglier now. - * - * The second element in the mapping pair is whether the stave - * with a certain ID should be mirrored around the x-axis. - */ - // map from Stave ID (1-indexed from other documents) to midpoint - // Do NOT add any zero midpoints, this is taken off separately - const std::map>& staveID_to_y_midpoint; - // lengths of staves, their midpoint, and their face - const std::vector& y_lengths; - const std::vector& x_midpoints; - double x_midpoint_spacing; - // which side of the disc do we place the stave? - // kSegmentedStave: staggering staves in z (see z_offsetStave) - // accessed via stave index, NOT stave ID - const std::vector& staveOnFront; - }; - - namespace OT_StavePositions { - const std::map> staveID_to_y_midpoint = { - {-2, {39.0, true}}, - {-1, {41.4, true}}, - {1, {41.4, true}}, - {2, {39.0, true}} - }; - const std::vector y_lengths = { - 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, - 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, - 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, - 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8 - }; - const std::vector x_midpoints = { - -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L - -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L - 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R - 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R - }; - const double x_midpoint_spacing = 4.5; // assume constant for now - const std::vector staveOnFront = - { - 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L - 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R - }; - } // namespace OT_StavePositions - - namespace ML_StavePositions { - // Use prelim numbers for now, these will change! TODO - const std::map> staveID_to_y_midpoint = { - {-3, {19.1, true}}, - {-2, {21.8, true}}, - {-1, {22.5, true}}, - {1, {22.5, true}}, - {2, {21.8, true}}, - {3, {19.1, true}} - }; - const std::vector y_lengths = { - 30.5, 44.5, 53.6, 60.0, 64.6, 29.5, 25.8, 25.0, - 25.0, 25.8, 29.5, 64.6, 60.0, 53.6, 44.5, 30.5 - }; - const std::vector x_midpoints = { - -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L - 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75 // R - }; - const double x_midpoint_spacing = 4.5; - const std::vector staveOnFront = - { - 1, 0, 1, 0, 1, 0, 1, 0, // L - 0, 1, 0, 1, 0, 1, 0, 1 // R - }; - } // namespace ML_StavePositions - - // Get stave configuration based on tracker type - inline StaveConfig getStaveConfig(bool isInnerDisk) { - if (isInnerDisk) { - return StaveConfig{ - ML_StavePositions::staveID_to_y_midpoint, - ML_StavePositions::y_lengths, - ML_StavePositions::x_midpoints, - ML_StavePositions::x_midpoint_spacing, - ML_StavePositions::staveOnFront - }; - } else { - return StaveConfig{ - OT_StavePositions::staveID_to_y_midpoint, - OT_StavePositions::y_lengths, - OT_StavePositions::x_midpoints, - OT_StavePositions::x_midpoint_spacing, - OT_StavePositions::staveOnFront - }; - } + // map from Stave ID (1-indexed from other documents) to midpoint + // Do NOT add any zero midpoints, this is taken off separately + const std::map>& staveID_to_y_midpoint; + // lengths of staves, their midpoint, and their face + const std::vector& y_lengths; + const std::vector& x_midpoints; + double x_midpoint_spacing; + // which side of the disc do we place the stave? + // kSegmentedStave: staggering staves in z (see z_offsetStave) + // accessed via stave index, NOT stave ID + const std::vector& staveOnFront; +}; + +namespace OT_StavePositions +{ +const std::map> staveID_to_y_midpoint = { + {-2, {39.0, true}}, + {-1, {41.4, true}}, + {1, {41.4, true}}, + {2, {39.0, true}}}; +const std::vector y_lengths = { + 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, + 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, + 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, + 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8}; +const std::vector x_midpoints = { + -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R + 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R +}; +const double x_midpoint_spacing = 4.5; // assume constant for now +const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R +}; +} // namespace OT_StavePositions + +namespace ML_StavePositions +{ +// Use prelim numbers for now, these will change! TODO +const std::map> staveID_to_y_midpoint = { + {-3, {19.1, true}}, + {-2, {21.8, true}}, + {-1, {22.5, true}}, + {1, {22.5, true}}, + {2, {21.8, true}}, + {3, {19.1, true}}}; +const std::vector y_lengths = { + 30.5, 44.5, 53.6, 60.0, 64.6, 29.5, 25.8, 25.0, + 25.0, 25.8, 29.5, 64.6, 60.0, 53.6, 44.5, 30.5}; +const std::vector x_midpoints = { + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75 // R +}; +const double x_midpoint_spacing = 4.5; +const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, // L + 0, 1, 0, 1, 0, 1, 0, 1 // R +}; +} // namespace ML_StavePositions + +// Get stave configuration based on tracker type +inline StaveConfig getStaveConfig(bool isInnerDisk) +{ + if (isInnerDisk) { + return StaveConfig{ + ML_StavePositions::staveID_to_y_midpoint, + ML_StavePositions::y_lengths, + ML_StavePositions::x_midpoints, + ML_StavePositions::x_midpoint_spacing, + ML_StavePositions::staveOnFront}; + } else { + return StaveConfig{ + OT_StavePositions::staveID_to_y_midpoint, + OT_StavePositions::y_lengths, + OT_StavePositions::x_midpoints, + OT_StavePositions::x_midpoint_spacing, + OT_StavePositions::staveOnFront}; } +} -} // namespace o2::ft3::ModuleConstants +} // namespace o2::ft3::ModuleConstants #endif // FT3MODULECONSTANTS_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 2003e2556e868..cc6accda3adb8 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -227,7 +227,8 @@ void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string motherVolume->AddNode(carbonFiberLayerVol2, 1, new TGeoTranslation(0, 0, 0 + zSeparation)); } -void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::string& name) { +void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::string& name) +{ // create reference circles at the inner and outer radius of the layer, for visualization purposes TGeoTube* innerCircle = new TGeoTube(mInnerRadius - 0.1, mInnerRadius + 0.1, 0.01); @@ -403,7 +404,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); } else if (ft3Params.layoutFT3 == kSegmented || - (ft3Params.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { + (ft3Params.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { FT3Module module; // layer structure @@ -413,7 +414,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); TGeoVolume* layerVol = nullptr; - // Add a little additional room in radius + // Add a little additional room in radius TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow + 2); @@ -447,7 +448,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs - double z_layer_thickness = // need to shift internally with this + double z_layer_thickness = // need to shift internally with this o2::ft3::ModuleConstants::staveTriangleHeight + o2::ft3::ModuleConstants::z_offsetStave(staveConfig.x_midpoint_spacing) + o2::ft3::ModuleConstants::siliconThickness + @@ -462,15 +463,14 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) 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); + std::string referenceCirclesName = "ReferenceCircles_Dir" + std::to_string(mDirection) + "_Layer" + std::to_string(mLayerNumber); createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes } // need the -0.5 added to local offset to ensure all sensor modules are inside the layer module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, - mOuterRadius, z_local_offset, staveConfig, layerVol); - // Finally put everything in the mother volume + mOuterRadius, z_local_offset, staveConfig, layerVol); + // Finally put everything in the mother volume auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); // need to shift outwards always, so + forwards and - backwards double z_offset_directional = mDirection ? z_local_offset : -z_local_offset; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx index 56db13bde5251..221136000a371 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -70,8 +70,8 @@ void FT3Module::initialize_materials() kaptonMed = new TGeoMedium("FT3_Kapton", 3, kaptonMat); // TODO: Check with Rene the exact type of carbon fiber - carbonFiberMat = new TGeoMaterial("FT3_Carbon", 12.0107, 6, 1.8); - carbonFiberMed = new TGeoMedium("FT3_Carbon", 6, carbonFiberMat); + carbonFiberMat = new TGeoMaterial("FT3_Carbon", 12.0107, 6, 1.8); + carbonFiberMed = new TGeoMedium("FT3_Carbon", 6, carbonFiberMat); // Epoxy: C18 H19 O3 auto* itsEpoxy = new TGeoMixture("FT3_Epoxy", 3); @@ -98,7 +98,7 @@ std::pair calculate_y_range( { double max_y_abs; double min_y_abs; - /* + /* * Have 5 cases: * (1) Stave wholly on the left of inner radius * (2) Stave wholly on the left, but within inner radius @@ -134,7 +134,7 @@ std::pair calculate_y_range( /* * This function is a helper function which will pad out the stave with sensors * until there is no more space available. - * + * * Arguments: * y_positions: a pair of vectors, where each vector contains pairs of * y position and stack height for the positive and negative y positions respectively. @@ -174,7 +174,7 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double y_top = y_ranges.first.first; } // fill positive y sensor positions - while ( (y_top + sensorStackHeight) <= max_sensor_y_abs ) { + while ((y_top + sensorStackHeight) <= max_sensor_y_abs) { y_positions.first.emplace_back(y_top, kSensorStack); y_top += sensorAbsStackYShift; } @@ -194,7 +194,7 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double y_bottom = y_ranges.second.first; } // fill in the sensors on negative y - while ( (y_bottom - sensorStackHeight) >= -max_sensor_y_abs ) { + while ((y_bottom - sensorStackHeight) >= -max_sensor_y_abs) { y_positions.second.emplace_back(y_bottom, kSensorStack); y_bottom -= sensorAbsStackYShift; } @@ -205,7 +205,7 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double * * Each array of 3 corresponds to x or z values of the 3 triangle vertices, * and the outer array corresponds to which triangle: - * + * * [x_outer, z_outer, x_inner, z_inner], each of which has three values */ std::array, 4> buildStaveTriangle(int direction) @@ -213,25 +213,26 @@ std::array, 4> buildStaveTriangle(int direction) // Set some constants for readability double d = Constants::effectiveCarbonThickness_Stave; double H = Constants::staveTriangleHeight; - /* + /* * Inner and outer vertices of the stave cross section triangle * all vertices are at y_mid, we simply extend the triangle into y dir. * We work in the local coordinate system of the stave, but still * call the coordinates x and z for readability. - * + * * 1. Get all local coordinates of the two triangle vertices * 2. Extrude a volume from the subtracted triangle cross section area * 3. Rotate the volume around the x-axis since it is by default in xy, * and extruded in z. Rotate by -90 for xz -> xy, otherwise xz -> x(-y) * 4. Translate the volume to the given position (arguments) - * - */ + * + */ std::array xv_inner, xv_outer, zv_inner, zv_outer; // calculate the coordinates of the triangle vertices // Top/bottom vertex (apex) xv_outer[0] = 0; zv_outer[0] = (direction == 1) ? -H - : H;; + : H; + ; // right xv_outer[1] = Constants::sensor2x1_width / 2 + Constants::staveSensorGap; zv_outer[1] = 0; @@ -242,13 +243,11 @@ std::array, 4> buildStaveTriangle(int direction) // now get inner vertices, shifted inwards by effective carbon thickness xv_inner[0] = xv_outer[0]; double z_shift_inner = d / Constants::sinTheta; - zv_inner[0] = (direction == 1) ? - zv_outer[0] + z_shift_inner - : zv_outer[0] - z_shift_inner; + zv_inner[0] = (direction == 1) ? zv_outer[0] + z_shift_inner + : zv_outer[0] - z_shift_inner; // face vertices, first right - zv_inner[1] = (direction == 1) ? - zv_outer[1] - d - : zv_outer[1] + d; + zv_inner[1] = (direction == 1) ? zv_outer[1] - d + : zv_outer[1] + d; double x_shift_abs = d / TMath::Tan(Constants::alpha / 2); xv_inner[1] = xv_outer[1] - x_shift_abs; // left @@ -263,11 +262,11 @@ std::array, 4> buildStaveTriangle(int direction) * onto which the sensor and its support will be glued. */ void FT3Module::addStaveVolume( - TGeoVolume* motherVolume, std::string volumeName, int direction, - unsigned* volume_count, double staveLength, - std::array, 4> staveTriangles, - std::pair& absAllowedYRange, - double x_mid, double y_mid, double z_stave_shift_forward) + TGeoVolume* motherVolume, std::string volumeName, int direction, + unsigned* volume_count, double staveLength, + std::array, 4> staveTriangles, + std::pair& absAllowedYRange, + double x_mid, double y_mid, double z_stave_shift_forward) { // The allowed y range is assumed to be non-negative. if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 || @@ -282,13 +281,13 @@ void FT3Module::addStaveVolume( double y_lower = y_mid - staveLength / 2; double y_upper = y_mid + staveLength / 2; bool splitStave = false; - if (y_lower > 0) { // This stave is fully above x-axis + if (y_lower > 0) { // This stave is fully above x-axis y_lower = std::max(y_lower, absAllowedYRange.first); y_upper = std::min(y_upper, absAllowedYRange.second); - } else if (y_upper < 0) { // stave entirely below x-axis + } else if (y_upper < 0) { // stave entirely below x-axis y_lower = std::max(y_lower, -absAllowedYRange.second); y_upper = std::min(y_upper, -absAllowedYRange.first); - } else { // Full range stave that goes across x-axis + } else { // Full range stave that goes across x-axis // Here we might have to cut the stave up into two pieces if (absAllowedYRange.first > 0) { // There is a minimum inner value --> Split stave @@ -301,7 +300,7 @@ void FT3Module::addStaveVolume( y_upper = std::min(y_upper, absAllowedYRange.second); } double staveLengthToUse = y_upper - y_lower; - /* + /* * create the extruded volumes from z=0 (later y=0 after rotation) to stave length * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later * @@ -309,35 +308,33 @@ void FT3Module::addStaveVolume( * and outer radius of the layer. */ TGeoXtru* staveFull = new TGeoXtru(2); - staveFull->SetName(( volumeName + "_Xtru_outer").c_str()); + staveFull->SetName((volumeName + "_Xtru_outer").c_str()); staveFull->DefinePolygon(3, staveTriangles[0].data(), staveTriangles[1].data()); staveFull->DefineSection(0, 0); staveFull->DefineSection(1, staveLengthToUse); TGeoXtru* staveInner = new TGeoXtru(2); - staveInner->SetName(( volumeName + "_Xtru_inner").c_str()); + staveInner->SetName((volumeName + "_Xtru_inner").c_str()); staveInner->DefinePolygon(3, staveTriangles[2].data(), staveTriangles[3].data()); staveInner->DefineSection(0, 0); staveInner->DefineSection(1, staveLengthToUse); TGeoCompositeShape* staveShape = new TGeoCompositeShape( (volumeName + "_shape").c_str(), - Form("%s - %s", staveFull->GetName(), staveInner->GetName()) - ); + Form("%s - %s", staveFull->GetName(), staveInner->GetName())); TGeoVolume* staveVolume = new TGeoVolume( (volumeName).c_str(), staveShape, - carbonFiberMed - ); + carbonFiberMed); TGeoRotation* rot = new TGeoRotation(); - rot->RotateX(-90); // lift from xy plane into xz plane - /* + rot->RotateX(-90); // lift from xy plane into xz plane + /* * After rotations the face of the stave lies in the xy-plane, * facing downwards for direction == 1 and upwards for direction == 0. * We still need to shift it in z to get the right staggered layout. * This means moving the staves that must be shifted in the opposite * direction they are facing: up for direction 1, and down for direction 0. - * + * * Unlike a regular node placement, we have to put the stave at its * starting point in y, not the midpoint. Hence, if we have the mirror, * the starting point is the upper y value, since that is the bottom @@ -380,12 +377,11 @@ void FT3Module::addDetectorVolume( motherVolume->AddNode( volume, *volume_count, - new TGeoTranslation( // midpoint of box to add + new TGeoTranslation( // midpoint of box to add x_mid, y_mid, - z_mid - ) // TGeoTranslation - ); // addNode + z_mid) // TGeoTranslation + ); // addNode (*volume_count)++; } @@ -398,16 +394,11 @@ void FT3Module::add2x1GlueVolume( unsigned* volume_count, double x_mid, double y_mid, double z_mid, std::string element_glued_to) { - std::string glue_name = "FT3glue_" + element_glued_to + "_" - + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" - + std::to_string(stave_idx) + "_" - + std::to_string(*volume_count); + std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); addDetectorVolume( motherVolume, glue_name, Constants::glueColor, volume_count, x_mid, y_mid, z_mid, - Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2 - ); + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2); } /* @@ -418,15 +409,11 @@ void FT3Module::add2x1CopperVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, unsigned* volume_count, double x_mid, double y_mid, double z_mid) { - std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" - + std::to_string(stave_idx) + "_" - + std::to_string(*volume_count); + std::string copper_name = "FT3Copper_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); addDetectorVolume( motherVolume, copper_name, Constants::CuColor, volume_count, x_mid, y_mid, z_mid, - Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2 - ); + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2); } /* @@ -437,26 +424,22 @@ void FT3Module::add2x1KaptonVolume( TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, unsigned* volume_count, double x_mid, double y_mid, double z_mid) { - std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" - + std::to_string(stave_idx) + "_" - + std::to_string(*volume_count); + std::string kapton_name = "FT3Kapton_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); addDetectorVolume( motherVolume, kapton_name, Constants::kaptonColor, volume_count, x_mid, y_mid, z_mid, - Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2 - ); + Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2); } /* * This function adds a single sensor (currently 2.5x3.2mm) to the given mother volume * at the given (x,y,z) position of the module. - * + * * Because the sensor has an inactive region of 0.2mm on one side, we also add a * separate volume for the inactive region, which will be either on the left or * or right dependent on the if the sensor is on the left or right in a 2x1 layout. * See FT3Module.h for more details on the layout. - * + * * Arguments: * motherVolume: the volume to which the sensor volume will be added * layerNumber: the layer number of the sensor, used for naming @@ -474,45 +457,37 @@ void FT3Module::addSingleSensorVolume( TGeoVolume* sensor; TGeoManager* geoManager = gGeoManager; // ACTIVE AREA - std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" - + std::to_string(stave_idx) + "_" - + std::to_string(*volume_count); + std::string sensor_name = "FT3Sensor_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, Constants::active_width / 2, - Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); sensor->SetLineColor(Constants::SiColor); sensor->SetFillColorAlpha(Constants::SiColor, 0.4); motherVolume->AddNode( sensor, *volume_count, - new TGeoTranslation( // midpoint of box to add + new TGeoTranslation( // midpoint of box to add active_x_mid, y_mid, - z_mid - ) // TGeoTranslation - ); // addNode + z_mid) // TGeoTranslation + ); // addNode (*volume_count)++; // INACTIVE STRIP ON LEFT OR RIGHT double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2) : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2); std::string sensor_inactive_name = - "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" - + std::to_string(direction) + "_" - + std::to_string(stave_idx) + "_" - + std::to_string(*volume_count); + "FT3Sensor_Inactive_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(stave_idx) + "_" + std::to_string(*volume_count); sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, - Constants::single_sensor_height / 2, Constants::siliconThickness / 2); + Constants::single_sensor_height / 2, Constants::siliconThickness / 2); sensor->SetLineColor(Constants::SiInactiveColor); sensor->SetFillColorAlpha(Constants::SiInactiveColor, 0.4); motherVolume->AddNode( sensor, *volume_count, - new TGeoTranslation( // midpoint of box to add + new TGeoTranslation( // midpoint of box to add inactive_x_mid, y_mid, - z_mid - ) // TGeoTranslation - ); // addNode + z_mid) // TGeoTranslation + ); // addNode (*volume_count)++; } @@ -522,23 +497,23 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction TGeoVolume* motherVolume) { LOG(debug) << "FT3Module: create_layout_staveGeo - Layer " - << layerNumber << ", Direction " << direction; + << layerNumber << ", Direction " << direction; FT3Module::initialize_materials(); auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); // First let's define some constants used throughout - /* + /* * we build the volume from the outside in, starting with the silicon, * then glue & materials towards the stave. Depending on direction, - * the distance from the center will be mirrored. - * + * the distance from the center will be mirrored. + * * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | * ----------------------------------------------------------------> z - * + * * Naturally, this will be mirrored for layers in the backwards direction, * such that the face of the sensors always face the interaction region. - * + * * Currently, we stipulate that the default stave face is at local z=0, * that is then shifted by the half air thickness encapsulating the layer * to avoid overlaps with the air and services. All offsets are @@ -569,7 +544,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction // initialise all y_positions, vector over all staves/columns std::vector y_positionsPosNeg; - unsigned volume_count = 0; // give each subvolume a unique ID + unsigned volume_count = 0; // give each subvolume a unique ID // stave triangle cross sections are the same for every stave (direction based) std::array, 4> staveTriangles = buildStaveTriangle(direction); // Create the stave volumes and fill the y positions where to put sensors on the stave @@ -581,7 +556,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction bool mirrorStaveAroundX = false; // default positive and negative starting points has a gap around x-axis for symmetry double stave_half_length = staveConfig.y_lengths[i_stave] / 2; - PositionRangeType y_ranges; + PositionRangeType y_ranges; if (ft3Params.placeSensorInMiddleOfStave) { /* * We want a sensor to cross over the x-axis for coverage at y=0 @@ -601,16 +576,16 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction {-Constants::stackGap / 2, -stave_half_length}}; } auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID); - if ( y_midpoint_it != staveConfig.staveID_to_y_midpoint.end() ) { + if (y_midpoint_it != staveConfig.staveID_to_y_midpoint.end()) { // there is a defined midpoint for this stave, use this for starting points - y_midpoint = y_midpoint_it->second.first; // avoid double map lookup + y_midpoint = y_midpoint_it->second.first; // avoid double map lookup mirrorStaveAroundX = y_midpoint_it->second.second; y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; } // Define tolerances for cutting staves and placing sensors - double tolerance_inner = -1000; // large negative number to allow given numbers + double tolerance_inner = -1000; // large negative number to allow given numbers double tolerance_outer = -1000; // cut staves on nominal inner radius if specified if (ft3Params.cutStavesOnNominalRadius_inner) { @@ -623,7 +598,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction /* * There are three cases in which we want to mirror the stave around the x-axis, * which correspond to the stave not going fully from + to - Rout in y. - * + * * (1) The inner tolerance is 0 (or positive) * a) AND either x_left or x_right lies within the inner radius * (2) The inner tolerance is large (allow stave placement as wished) @@ -633,8 +608,8 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction double x_right = x_left + Constants::sensor2x1_width; std::pair absAllowedYRange = calculate_y_range(x_left, x_right, Rin, Rout); - - /* + + /* * Shift allowed range by tolerance. Note that both values in the range must * be non-negative, and if the inner is not, then set it to 0. This just means * that there is no lower limit. The upper limit must however be larger than 0, @@ -653,25 +628,22 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction } // Get whether the stave is shifted backward or not before creating - double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? - 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); - double z_stave_shift_forward = // move staves more inward to fit in layer volume + double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + double z_stave_shift_forward = // move staves more inward to fit in layer volume -z_offset_to_carbon_face + z_stave_shift_abs; std::string stave_volume_name = "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + - "_" + std::to_string(direction); + "_" + std::to_string(direction); addStaveVolume( motherVolume, stave_volume_name, direction, &volume_count, staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, - staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward - ); + staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward); // Now create the mirrored stave if (mirrorStaveAroundX) { addStaveVolume( motherVolume, stave_volume_name + "_mirrored", direction, &volume_count, staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, - staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward - ); + staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward); } // now add the sensor positions on the stave @@ -689,14 +661,14 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction /* * Declare an offset multiplier for the z offsets, used for distinguishing * sensors facing either forward or backward. - * + * * In the stave layout, all sensors face inward, and isFront * refers to whether a stave is shifted backwards or not. Thus, * we decide the offset multiplier only with direction, to * keep the face facing inwards. */ bool isFront; - if (direction == 1) { // direction = 1 is forward + if (direction == 1) { // direction = 1 is forward isFront = staveConfig.staveOnFront[i_stave]; } else { isFront = !(staveConfig.staveOnFront[i_stave]); @@ -711,10 +683,10 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction : -Constants::z_offsetStave(staveConfig.x_midpoint_spacing); } - for (int y_sign = -1; y_sign < 2; y_sign+=2) { + for (int y_sign = -1; y_sign < 2; y_sign += 2) { // place sensors at positive and negative y - const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first - : y_positionsPosNeg[i_stave].second; + const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first + : y_positionsPosNeg[i_stave].second; // define starting midpoint: y = y_start +- distance to middle of sensor for (unsigned i_y_pos = 0; i_y_pos < positions.size(); i_y_pos++) { double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2; @@ -725,45 +697,37 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; addSingleSensorVolume( motherVolume, layerNumber, direction, i_stave, &volume_count, - x_mid - Constants::active_width / 2, y_mid, z_mid, true - ); + x_mid - Constants::active_width / 2, y_mid, z_mid, true); // right single sensor of the 2x1 addSingleSensorVolume( motherVolume, layerNumber, direction, i_stave, &volume_count, - x_mid + Constants::active_width / 2, y_mid, z_mid, false - ); + x_mid + Constants::active_width / 2, y_mid, z_mid, false); // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( motherVolume, layerNumber, direction, i_stave, &volume_count, - x_mid, y_mid, z_mid, "SiCu" - ); + x_mid, y_mid, z_mid, "SiCu"); // ------------ (3) Copper layer (FPC) ------------ z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift; add2x1CopperVolume( motherVolume, layerNumber, direction, i_stave, &volume_count, - x_mid, y_mid, z_mid - ); + x_mid, y_mid, z_mid); // ------------ (4) Kapton layer (FPC) ------------ z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift; add2x1KaptonVolume( motherVolume, layerNumber, direction, i_stave, &volume_count, - x_mid, y_mid, z_mid - ); + x_mid, y_mid, z_mid); // ------------ (5) Epoxy glue layer between stave and Kapton ------------ z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift; add2x1GlueVolume( motherVolume, layerNumber, direction, i_stave, &volume_count, - x_mid, y_mid, z_mid, "CarbonKapton" - ); + x_mid, y_mid, z_mid, "CarbonKapton"); // increment to next sensor: (height + gap of one sensor) y_mid += y_sign * (Constants::sensor2x1_height + Constants::sensor2x1_gap); - } // sensors in stack - } // for y_sign (writing of positive or negative y positions) - } // i_y_pos - } // i_stave - - + } // sensors in stack + } // for y_sign (writing of positive or negative y positions) + } // i_y_pos + } // i_stave } void FT3Module::create_layout(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume) @@ -1428,7 +1392,8 @@ void FT3Module::createModule(double mZ, int layerNumber, int direction, double R void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, - TGeoVolume* motherVolume) { + TGeoVolume* motherVolume) +{ LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber << " at z=" << mZ << ", Direction " << direction; create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout,