Project
Loading...
Searching...
No Matches
FT3Module.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
17#include <TGeoManager.h>
18#include <TGeoMedium.h>
19#include <TGeoBBox.h>
20#include <TGeoXtru.h>
21#include <TGeoMatrix.h>
22#include <TGeoCompositeShape.h>
23#include <Framework/Logger.h>
24#include <cmath>
25#include <iostream>
26#include <limits>
27#include <map>
28#include <vector>
29#include <set>
30#include <algorithm>
31#include <utility>
32
34
35double calculate_y_circle(double x, double radius)
36{
37 return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0;
38}
39
40std::pair<double, double> calculate_y_range(
41 double x_left, double x_right, double Rin, double Rout)
42{
43 double max_y_abs;
44 double min_y_abs;
45 /*
46 * Have 5 cases:
47 * (1) Stave wholly on the left of inner radius
48 * (2) Stave wholly on the left, but within inner radius
49 * (3) Stave crosses the middle x=0
50 * (4) Stave wholly on the right, but within inner radius
51 * (5) Stave wholly on the right of inner radius
52 */
53 if (x_right < -Rin) {
54 // Stave is completely on the left of inner radius
55 min_y_abs = 0;
56 max_y_abs = calculate_y_circle(x_left, Rout);
57 } else if (x_left < -Constants::sensor2x1_width) {
58 // Stave is completely on the left, but within inner radius
59 min_y_abs = calculate_y_circle(x_right, Rin);
60 max_y_abs = calculate_y_circle(x_left, Rout);
61 } else if (x_left < 0) {
62 // Stave crosses the middle x=0
63 min_y_abs = Rin;
64 // x_right should be > 0, but might have FLP issues, so do abs nonetheless
65 max_y_abs = calculate_y_circle(std::max(std::abs(x_left), std::abs(x_right)), Rout);
66 } else if (x_left < Rin) {
67 // Stave is completely on the right, but within inner radius
68 min_y_abs = calculate_y_circle(x_left, Rin);
69 max_y_abs = calculate_y_circle(x_right, Rout);
70 } else {
71 // Stave is completely on the right of inner radius
72 min_y_abs = 0.;
73 max_y_abs = calculate_y_circle(x_right, Rout);
74 }
75 return {min_y_abs, max_y_abs};
76}
77
78/*
79 * Greedy fill: determine the positions of sensors on the stave
80 * by adding sensors until there is no more space available.
81 *
82 * Arguments:
83 * y_positions: a pair of vectors, where each vector contains pairs of
84 * y position and stack height for the positive and negative y positions respectively.
85 * This argument will be appended with the new sensor positions and stack heights.
86 * kSensorStack: the number of sensors to be stacked on top of each other
87 * y_ranges: the y positions to start and end placing sensors,
88 * for positive and negative y respectively
89 * absAllowedYRange: the absolute y range allowed for placing sensors,
90 * used to cut placement if they go past allowed tolerances
91 */
92void FT3Module::fill_stave_greedy(PosNegPositionTypes& y_positions, unsigned kSensorStack,
93 PositionRangeType y_ranges,
94 std::pair<double, double>& absAllowedYRange)
95{
96 // start with upper half of the stave, then mirror to the bottom half
97 // add the height of kSensorStack sensors + the gaps in between them
98 double sensorStackHeight = Constants::getStackHeight(kSensorStack);
99 double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap;
100
101 // in case a big tolerance is given, cut on the given range instead
102 double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second);
103
104 double y_top; // top half of the xy grid, y>0
105 // either start at given value (adjusted for tolerance), or at last placed sensors
106 if (!y_positions.first.empty()) { // sensors already placed
107 double previousStackHeight = Constants::getStackHeight(y_positions.first.back().second);
108 y_top = y_positions.first.back().first + previousStackHeight + Constants::stackGap;
109 } else if (absAllowedYRange.first > 0) {
110 // there is a minimum inner value --> start at the max of the two
111 y_top = std::max(absAllowedYRange.first, y_ranges.first.first);
112 } else {
113 // No inner minimum value, start at given value
114 y_top = y_ranges.first.first;
115 }
116 // fill positive y sensor positions
117 while ((y_top + sensorStackHeight) <= max_sensor_y_abs) {
118 y_positions.first.emplace_back(y_top, kSensorStack);
119 y_top += sensorAbsStackYShift;
120 }
121
122 // now we do the same for the negative y positions
123 // they do not have to be exactly mirrored, hence done separately
124 double y_bottom;
125 if (!y_positions.second.empty()) {
126 // subtract instead to move further down
127 double previousStackHeight = Constants::getStackHeight(y_positions.second.back().second);
128 y_bottom = y_positions.second.back().first - previousStackHeight - Constants::stackGap;
129 } else if (absAllowedYRange.first > 0) {
130 // there is a minimum inner value --> start at the min of the two
131 y_bottom = std::min(-absAllowedYRange.first, y_ranges.second.first);
132 } else {
133 // No inner minimum value, start at given value
134 y_bottom = y_ranges.second.first;
135 }
136 // fill in the sensors on negative y
137 while ((y_bottom - sensorStackHeight) >= -max_sensor_y_abs) {
138 y_positions.second.emplace_back(y_bottom, kSensorStack);
139 y_bottom -= sensorAbsStackYShift;
140 }
141}
142
143/*
144 * Exact fill: read the module positions of one stave straight out of the
145 * tabulated layout. The counterpart to fill_stave_greedy, without any of its
146 * checks -- the table is validated before it reaches the geometry, so there is
147 * nothing here to cut, clip or derive.
148 *
149 * fills: the stave's fills, each an uninterrupted stretch of modules. A stave
150 * split by the beam pipe has one fill below the hole and one above it;
151 * nothing is mirrored, since the layout is symmetric about the y-axis
152 * but not about the x-axis.
153 *
154 * Returns one list holding every module of the stave, at negative y as well,
155 * each stored by its BOTTOM edge.
156 */
157PositionTypes FT3Module::fill_stave_exact(const std::vector<Constants::StaveFill>& fills)
158{
159 PositionTypes y_positions;
160 for (const auto& fill : fills) {
161 double y_bottom = fill.yStart;
162 for (unsigned kSensorStack : fill.stackHeights) {
163 y_positions.emplace_back(y_bottom, kSensorStack);
164 y_bottom += Constants::getStackHeight(kSensorStack) + Constants::stackGap;
165 }
166 }
167 return y_positions;
168}
169
170/*
171 * Create the vertices of the triangles that make up the stave cross section
172 *
173 * Each array of 3 corresponds to x or z values of the 3 triangle vertices,
174 * and the outer array corresponds to which triangle:
175 *
176 * [x_outer, z_outer, x_inner, z_inner], each of which has three values
177 */
178std::array<std::array<double, 3>, 4> buildStaveTriangle(int direction)
179{
180 // Set some constants for readability
183 /*
184 * Inner and outer vertices of the stave cross section triangle
185 * all vertices are at y_mid, we simply extend the triangle into y dir.
186 * We work in the local coordinate system of the stave, but still
187 * call the coordinates x and z for readability.
188 *
189 * 1. Get all local coordinates of the two triangle vertices
190 * 2. Extrude a volume from the subtracted triangle cross section area
191 * 3. Rotate the volume around the x-axis since it is by default in xy,
192 * and extruded in z. Rotate by -90 for xz -> xy, otherwise xz -> x(-y)
193 * 4. Translate the volume to the given position (arguments)
194 *
195 */
196 std::array<double, 3> xv_inner, xv_outer, zv_inner, zv_outer;
197 // calculate the coordinates of the triangle vertices
198 // Top/bottom vertex (apex)
199 xv_outer[0] = 0;
200 zv_outer[0] = (direction == 1) ? -H
201 : H;
202 ;
203 // right
205 zv_outer[1] = 0;
206 // left
207 xv_outer[2] = -xv_outer[1];
208 zv_outer[2] = 0;
209
210 // now get inner vertices, shifted inwards by effective carbon thickness
211 xv_inner[0] = xv_outer[0];
212 double z_shift_inner = d / Constants::sinTheta;
213 zv_inner[0] = (direction == 1) ? zv_outer[0] + z_shift_inner
214 : zv_outer[0] - z_shift_inner;
215 // face vertices, first right
216 zv_inner[1] = (direction == 1) ? zv_outer[1] - d
217 : zv_outer[1] + d;
218 double x_shift_abs = d / TMath::Tan(Constants::alpha / 2);
219 xv_inner[1] = xv_outer[1] - x_shift_abs;
220 // left
221 zv_inner[2] = zv_inner[1];
222 xv_inner[2] = -xv_inner[1];
223
224 return {xv_outer, zv_outer, xv_inner, zv_inner};
225}
226
227/*
228 * This function creates a carbon fibre volume for the stave,
229 * onto which the sensor and its support will be glued.
230 */
231void FT3Module::addStaveVolume(
232 TGeoVolume* motherVolume, std::string volumeName, int direction,
233 unsigned* volume_count, double staveLength,
234 std::array<std::array<double, 3>, 4> staveTriangles,
235 std::pair<double, double>& absAllowedYRange,
236 double x_mid, double y_mid, double z_stave_shift_forward)
237{
238 // The allowed y range is assumed to be non-negative.
239 if (absAllowedYRange.first < 0 || absAllowedYRange.second < 0 ||
240 absAllowedYRange.first >= absAllowedYRange.second) {
241 LOG(error) << "Invalid allowed y range in addStaveVolume(): ("
242 << absAllowedYRange.first << ", " << absAllowedYRange.second
243 << "). Both values must be non-negative and the first "
244 << "value must be less than the second value.";
245 return;
246 }
247 // Set the lower and upper y values of the stave:
248 double y_lower = y_mid - staveLength / 2;
249 double y_upper = y_mid + staveLength / 2;
250 bool splitStave = false;
251 if (y_lower > 0) { // This stave is fully above x-axis
252 y_lower = std::max(y_lower, absAllowedYRange.first);
253 y_upper = std::min(y_upper, absAllowedYRange.second);
254 } else if (y_upper < 0) { // stave entirely below x-axis
255 y_lower = std::max(y_lower, -absAllowedYRange.second);
256 y_upper = std::min(y_upper, -absAllowedYRange.first);
257 } else { // Full range stave that goes across x-axis
258 // Here we might have to cut the stave up into two pieces
259 if (absAllowedYRange.first > 0) {
260 // There is a minimum inner value --> Split stave
261 splitStave = true;
262 y_lower = absAllowedYRange.first;
263 } else {
264 // regular stave, use full length, but don't forget outer cut
265 y_lower = std::max(y_lower, -absAllowedYRange.second);
266 }
267 y_upper = std::min(y_upper, absAllowedYRange.second);
268 }
269 double staveLengthToUse = y_upper - y_lower;
270 /*
271 * create the extruded volumes from z=0 (later y=0 after rotation) to stave length
272 * and not from midpoint - staveLength/2 to midpoint + staveLength/2, translate later
273 *
274 * Note also that we first need to check if the length is allowed given the inner
275 * and outer radius of the layer.
276 */
277 TGeoXtru* staveFull = new TGeoXtru(2);
278 staveFull->SetName((volumeName + "_Xtru_outer").c_str());
279 staveFull->DefinePolygon(3, staveTriangles[0].data(), staveTriangles[1].data());
280 staveFull->DefineSection(0, 0);
281 staveFull->DefineSection(1, staveLengthToUse);
282
283 TGeoXtru* staveInner = new TGeoXtru(2);
284 staveInner->SetName((volumeName + "_Xtru_inner").c_str());
285 staveInner->DefinePolygon(3, staveTriangles[2].data(), staveTriangles[3].data());
286 staveInner->DefineSection(0, 0);
287 staveInner->DefineSection(1, staveLengthToUse);
288
289 TGeoCompositeShape* staveShape = new TGeoCompositeShape(
290 (volumeName + "_shape").c_str(),
291 Form("%s - %s", staveFull->GetName(), staveInner->GetName()));
292 TGeoVolume* staveVolume = new TGeoVolume(
293 (volumeName).c_str(),
294 staveShape,
295 getMedium(Materials::MaterialID::CarbonFiber));
296 const int carbonFiberColor = Materials::materials.at(Materials::MaterialID::CarbonFiber).colour;
297 staveVolume->SetLineColor(carbonFiberColor);
298 staveVolume->SetFillColorAlpha(carbonFiberColor, 0.4);
299
300 TGeoRotation* rot = new TGeoRotation();
301 rot->RotateX(-90); // lift from xy plane into xz plane
302 /*
303 * After rotations the face of the stave lies in the xy-plane,
304 * facing downwards for direction == 1 and upwards for direction == 0.
305 * We still need to shift it in z to get the right staggered layout.
306 * This means moving the staves that must be shifted in the opposite
307 * direction they are facing: up for direction 1, and down for direction 0.
308 *
309 * Unlike a regular node placement, we have to put the stave at its
310 * starting point in y, not the midpoint. Hence, if we have the mirror,
311 * the starting point is the upper y value, since that is the bottom
312 * of the mirrored stave -- by the outer radius
313 */
314 double z_shift = (direction == 1) ? z_stave_shift_forward : -z_stave_shift_forward;
315 TGeoCombiTrans* combiTrans =
316 new TGeoCombiTrans(x_mid, y_lower, z_shift, rot);
317 motherVolume->AddNode(staveVolume,
318 *volume_count,
319 combiTrans);
320 (*volume_count)++;
321
322 // if the stave needs to be split, reuse the same volume on opposite side
323 if (splitStave) {
324 TGeoCombiTrans* combiTransSplit =
325 new TGeoCombiTrans(x_mid, -y_upper, z_shift, rot);
326 motherVolume->AddNode(staveVolume,
327 *volume_count,
328 combiTransSplit);
329 (*volume_count)++;
330 }
331}
332
333/*
334 * Generic helper function that adds a box at the given position with
335 * the given dimensions to the given mother volume, with the given color and name.
336 */
337
338void FT3Module::addDetectorVolume(
339 TGeoVolume* motherVolume, std::string volumeName, int color, TGeoMedium* med,
340 unsigned volume_count, double x_mid, double y_mid, double z_mid,
341 double x_half_length, double y_half_length, double z_half_length, double rotX)
342{
343 TGeoManager* geoManager = gGeoManager;
344 TGeoVolume* volume = geoManager->MakeBox(volumeName.c_str(), med, x_half_length,
345 y_half_length, z_half_length);
346 volume->SetLineColor(color);
347 volume->SetFillColorAlpha(color, 0.4);
348 if (rotX == 0.) {
349 motherVolume->AddNode(
350 volume,
351 volume_count,
352 new TGeoTranslation( // midpoint of box to add
353 x_mid,
354 y_mid,
355 z_mid) // TGeoTranslation
356 ); // addNode
357 } else {
358 motherVolume->AddNode(
359 volume,
360 volume_count,
361 new TGeoCombiTrans("",
362 x_mid,
363 y_mid,
364 z_mid,
365 new TGeoRotation("", 0., rotX, 0.)) // TGeoCombiTrans
366 ); // addNode
367 }
368}
369
370/*
371 * This function adds a glue volume between two element layers,
372 * immediately for a whole 2x1 layout, under both the active and inactive region.
373 */
374void FT3Module::add2x1GlueVolume(
375 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
376 unsigned volume_count, double x_mid, double y_mid, double z_mid,
377 std::string element_glued_to)
378{
379 std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count);
380 addDetectorVolume(
381 motherVolume, glue_name, Materials::materials.at(Materials::MaterialID::Epoxy).colour,
382 getMedium(Materials::MaterialID::Epoxy), volume_count,
383 x_mid, y_mid, z_mid,
385}
386
387/*
388 * This function adds a copper volume onto which the silicon sensor is glued.
389 * As with the glue, this is a whole 2x1 layout volume.
390 */
391void FT3Module::add2x1CopperVolume(
392 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
393 unsigned volume_count, double x_mid, double y_mid, double z_mid)
394{
395 std::string copper_name = "FT3Copper_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count);
396 addDetectorVolume(
397 motherVolume, copper_name, Materials::materials.at(Materials::MaterialID::Copper).colour,
398 getMedium(Materials::MaterialID::Copper), volume_count,
399 x_mid, y_mid, z_mid,
401}
402
403/*
404 * This function adds a kapton volume behind the copper, which represents the ???
405 * As with copper and glue, this is a whole 2x1 layout volume.
406 */
407void FT3Module::add2x1KaptonVolume(
408 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
409 unsigned volume_count, double x_mid, double y_mid, double z_mid)
410{
411 std::string kapton_name = "FT3Kapton_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count);
412 addDetectorVolume(
413 motherVolume, kapton_name, Materials::materials.at(Materials::MaterialID::Kapton).colour,
414 getMedium(Materials::MaterialID::Kapton), volume_count,
415 x_mid, y_mid, z_mid,
417}
418
419/*
420 * This function adds a single sensor (currently 2.5x3.2cm) to the given mother volume
421 * at the given (x,y,z) position of the module.
422 *
423 * Because the sensor has an inactive region of 2mm on one side, we also add a
424 * separate volume for the inactive region, which will be either on the left or
425 * or right dependent on the if the sensor is on the left or right in a 2x1 layout.
426 * See FT3Module.h for more details on the layout.
427 *
428 * Arguments:
429 * motherVolume: the volume to which the sensor volume will be added
430 * layerNumber: the layer number of the sensor, used for naming
431 * direction: the direction of the sensor (forward or backward eta), used for naming
432 * x_mid: the x position of the center of the sensor volume
433 * y_mid: the y position of the center of the sensor volume
434 * z_mid: the z position of the center of the sensor volume
435 * isLeft: whether the sensor is on the left or right in the 2x1 layout
436 */
437void FT3Module::addSingleSensorVolume(
438 TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx,
439 unsigned volume_count, double active_x_mid, double y_mid, double z_mid,
440 bool isLeft)
441{
442 TGeoVolume* sensor;
443 TGeoManager* geoManager = gGeoManager;
444 TGeoMedium* siliconMed = getMedium(Materials::MaterialID::Silicon);
445 // ACTIVE AREA
446 // Sensor thickness is along the Y axis; this convention is used in digitisation by barrels and disks
447 std::string sensor_name = "FT3Sensor_Active_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count);
448 addDetectorVolume(
449 motherVolume, sensor_name, Materials::materials.at(Materials::MaterialID::Silicon).colour, siliconMed,
450 volume_count, active_x_mid, y_mid, z_mid,
452
453 // INACTIVE STRIP ON LEFT OR RIGHT
454 double inactive_x_mid = isLeft ? (active_x_mid - Constants::active_width / 2 - Constants::inactive_width / 2)
455 : (active_x_mid + Constants::active_width / 2 + Constants::inactive_width / 2);
456 std::string sensor_inactive_name = "FT3Sensor_Inactive_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count);
457 sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2,
459 addDetectorVolume(
460 motherVolume, sensor_inactive_name, Materials::SiInactiveColor, siliconMed,
461 volume_count, inactive_x_mid, y_mid, z_mid,
463}
464
465/*
466 * Look up a stave's y midpoint and whether it is built as two pieces mirrored
467 * about the x-axis. Returns false for a stave absent from the map, which sits
468 * on y=0 in one piece and leaves both outputs untouched.
469 */
470bool staveMidpointAndMirror(const Constants::StaveConfig& staveConfig, int staveID,
471 double& y_midpoint, bool& mirrorStaveAroundX)
472{
473 auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID);
474 if (y_midpoint_it == staveConfig.staveID_to_y_midpoint.end()) {
475 return false;
476 }
477 y_midpoint = y_midpoint_it->second.first; // avoid double map lookup
478 mirrorStaveAroundX = y_midpoint_it->second.second;
479 return true;
480}
481
482/*
483 * FR4 + Cu end-of-stave card at the outer-radius tip of a disk stave.
484 * Mirrors the TRK barrel card: an FR4 board with evenly spaced copper planes.
485 * Local card axes in the (layer) mother frame: x = stave width, y = stave axis
486 * (radial at the tip), z = beam. The card sits downstream of the stave (away
487 * from the IP), just past the disk envelope, in front of the connection disk.
488 */
489void FT3Module::addEndOfStaveCard(
490 TGeoVolume* motherVolume, const std::string& name, int direction,
491 unsigned volume_count, double x_mid, double y_tip, double z_sensor,
492 bool isML, double cuThickness)
493{
495 if (cuThickness * c.nCopperLayers >= c.thickness) {
496 LOG(fatal) << "FT3 disk EoS card: Cu " << cuThickness << " cm x " << c.nCopperLayers
497 << " planes does not fit in the " << c.thickness << " cm card";
498 }
499
500 // FR4 board (x = width, y = thickness, z = length)
501 TGeoVolume* cardVol = gGeoManager->MakeBox(name.c_str(), getMedium(Materials::MaterialID::FR4),
502 c.width / 2, c.thickness / 2, c.length / 2);
503 cardVol->SetLineColor(kGreen + 3);
504
505 // copper planes spread over the thickness (y), outermost two flush with the faces
506 TGeoVolume* planeVol = gGeoManager->MakeBox((name + "_Cu").c_str(), getMedium(Materials::MaterialID::Copper),
507 c.width / 2, cuThickness / 2, c.length / 2);
508 planeVol->SetLineColor(kOrange + 7);
509 const double span = c.thickness - cuThickness;
510 for (int i = 0; i < c.nCopperLayers; ++i) {
511 const double y = (c.nCopperLayers > 1) ? -span / 2 + i * span / (c.nCopperLayers - 1) : 0.;
512 cardVol->AddNode(planeVol, i, new TGeoTranslation(0, y, 0));
513 }
514
515 // z: IP-facing edge starts just after the sensors (z_sensor), body extends
516 // downstream (away from the IP), toward the connection disk.
517 // y: sit just radially OUTSIDE the stave outer tip so the long card runs past
518 // the carbon stave along z without overlapping it.
519 const double s = (direction == 1) ? 1.0 : -1.0; // downstream sign
520 const double zCard = z_sensor + s * (c.zGap + c.length / 2);
521 const double yClear = 0.05; // radial clearance to the stave tip
522 const double yCard = y_tip + (y_tip >= 0 ? 1.0 : -1.0) * (c.thickness / 2 + yClear);
523 motherVolume->AddNode(cardVol, volume_count, new TGeoTranslation(x_mid, yCard, zCard));
524}
525
526/*
527 * Create the carbon shell of one stave, staggered in z, plus the mirrored one
528 * when the stave is built as two pieces.
529 */
530void FT3Module::add_stave_volumes(
531 TGeoVolume* motherVolume, int layerNumber, int direction,
532 const Constants::StaveConfig& staveConfig, unsigned i_stave,
533 const std::array<std::array<double, 3>, 4>& staveTriangles,
534 double z_offset_to_carbon_face, std::pair<double, double>& absAllowedYRange,
535 double y_midpoint, bool mirrorStaveAroundX, unsigned* staveVolumeCount)
536{
537 // Get whether the stave is shifted backward or not before creating
538 double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing);
539 double z_stave_shift_forward = // move staves more inward to fit in layer volume
540 -z_offset_to_carbon_face + z_stave_shift_abs;
541 std::string stave_volume_name =
542 "FT3_Stave_" + std::to_string(direction) + "_" + std::to_string(layerNumber) +
543 "_" + std::to_string(i_stave);
544
545 addStaveVolume(
546 motherVolume, stave_volume_name, direction, staveVolumeCount,
547 staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange,
548 staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward);
549 // Now create the mirrored stave
550 if (mirrorStaveAroundX) {
551 addStaveVolume(
552 motherVolume, stave_volume_name + "_mirrored", direction, staveVolumeCount,
553 staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange,
554 staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward);
555 }
556
557 // End-of-stave card at the outer-radius tip of each stave PIECE, placed
558 // downstream of the stave in front of the connection disk. A whole stave gets
559 // one card (the +y "top" tip). A split stave is built as two pieces (main +
560 // mirror), each reaching the outer radius at opposite ends, so each piece gets
561 // its own card at its outer tip (+y for the main, -y for the mirror).
562 // Shared by the exact and greedy paths; the disk layer envelope is extended
563 // downstream in FT3Layer::createLayer to contain the cards.
564 auto& ft3Params = o2::ft3::FT3BaseParam::Instance();
565 if (ft3Params.addDiskEosCards) {
566 const bool isML = staveConfig.isML;
567 const double cuT = isML ? ft3Params.ft3EosCardCuThicknessML : ft3Params.ft3EosCardCuThicknessOT;
568 // Actual outer-radius tip of the stave: greedy clamps the stave to
569 // absAllowedYRange.second, exact leaves it unbounded (DBL_MAX), so take the
570 // smaller of the geometric end and the radial clamp.
571 const double yTip = std::min(y_midpoint + staveConfig.y_lengths[i_stave] / 2,
572 absAllowedYRange.second);
573 // Per-stave sensor silicon z in the layer frame, reconstructed from the
574 // carbon-face offset exactly as the sensor-placement loop does, so the card
575 // starts right after the sensors.
576 const double z_offset_to_silicon = z_offset_to_carbon_face +
580 const double zOffMult = (direction == 1) ? -1.0 : 1.0;
581 double zStaveShiftSensors = 0.0;
582 if (!staveConfig.staveOnFront[i_stave]) {
583 zStaveShiftSensors = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing)
585 }
586 const double zSensor = z_offset_to_silicon * zOffMult + zStaveShiftSensors;
587 const std::string cardBase = "FT3_EOSdiskCard_" + std::to_string(direction) + "_" +
588 std::to_string(layerNumber) + "_" + std::to_string(i_stave);
589 addEndOfStaveCard(motherVolume, cardBase + "_pos", direction, (*staveVolumeCount)++,
590 staveConfig.x_midpoints[i_stave], +yTip, zSensor, isML, cuT);
591 // split stave: the mirror piece reaches the outer radius at -y, give it a card too
592 if (mirrorStaveAroundX) {
593 addEndOfStaveCard(motherVolume, cardBase + "_neg", direction, (*staveVolumeCount)++,
594 staveConfig.x_midpoints[i_stave], -yTip, zSensor, isML, cuT);
595 }
596 }
597}
598
599/*
600 * Exact layout: build every stave of the layer from the tabulated layout in
601 * exactStaveFills, the counterpart to build_staves_greedy.
602 *
603 * Deliberately stupid: the stave is built to the length y_lengths gives it and
604 * the modules sit exactly where the table says. No tolerance is applied, the
605 * stave is not cut on the layer radii, no stave is skipped and nothing is
606 * mirrored, so none of Rin, Rout or the staveTol parameters are needed here.
607 * Whatever produced the table is responsible for it fitting the layer.
608 */
609void FT3Module::build_staves_exact(
610 TGeoVolume* motherVolume, int layerNumber, int direction,
611 const Constants::StaveConfig& staveConfig,
612 const std::array<std::array<double, 3>, 4>& staveTriangles,
613 double z_offset_to_carbon_face,
614 std::vector<PosNegPositionTypes>& y_positionsPosNeg,
615 unsigned& staveVolumeCount)
616{
617 // number of modules per stack height, only used for logging. Keyed by height
618 // rather than indexed by kSensorsPerStack, since the table is free to use
619 // heights that are not in that list.
620 std::map<unsigned, unsigned> nSensorStackTotal;
621 for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) {
622 const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size());
623
624 // a stave in the map is built as two pieces, one either side of the beam pipe
625 double y_midpoint = 0.;
626 bool mirrorStaveAroundX = false;
627 staveMidpointAndMirror(staveConfig, staveID, y_midpoint, mirrorStaveAroundX);
628
629 // no radial limit, so addStaveVolume cuts nothing off the stave
630 std::pair<double, double> absAllowedYRange = {0., std::numeric_limits<double>::max()};
631
632 add_stave_volumes(motherVolume, layerNumber, direction, staveConfig, i_stave,
633 staveTriangles, z_offset_to_carbon_face, absAllowedYRange,
634 y_midpoint, mirrorStaveAroundX, &staveVolumeCount);
635
636 /*
637 * Every module goes in the positive-y list, whatever the sign of its y, and
638 * the negative-y list stays empty. The placement loop reads that list with
639 * y_sign = +1 and only ever adds to the stored bottom edge, so it does not
640 * care that some of those edges are negative.
641 */
642 y_positionsPosNeg.emplace_back(fill_stave_exact(staveConfig.exactStaveFills[i_stave]),
643 PositionTypes{});
644
645 std::map<unsigned, unsigned> nSensorStackCount;
646 for (const auto& [y_bottom, kSensorStack] : y_positionsPosNeg.back().first) {
647 nSensorStackCount[kSensorStack]++;
648 nSensorStackTotal[kSensorStack]++;
649 }
650 std::string moduleDebugStr = "Module size counts for layer " + std::to_string(layerNumber) + " in direction " + std::to_string(direction) + ":\n";
651 for (const auto& [kSensorStack, nModules] : nSensorStackCount) {
652 moduleDebugStr += "\t" + std::to_string(nModules) + " modules with " + std::to_string(kSensorStack) + " sensors stacked\n";
653 }
654 LOG(debug) << moduleDebugStr;
655 }
656 std::string totalModuleInfoStr =
657 "Total module size counts for layer " + std::to_string(layerNumber) +
658 " in direction " + std::to_string(direction) + ":\n";
659 for (const auto& [kSensorStack, nModules] : nSensorStackTotal) {
660 totalModuleInfoStr += "\t" + std::to_string(nModules) + " modules with " + std::to_string(kSensorStack) + " sensors stacked\n";
661 }
662 LOG(info) << totalModuleInfoStr;
663}
664
665/*
666 * Greedy layout: work out the module positions from the layer radii and the
667 * stave length, filling each stave from the middle outwards with the stack
668 * sizes in kSensorsPerStack. Since it chooses the positions itself, this is
669 * the path that applies the radial tolerances.
670 *
671 * Extracted verbatim from create_layout_staveGeo; the body is unchanged.
672 */
673void FT3Module::build_staves_greedy(
674 TGeoVolume* motherVolume, int layerNumber, int direction, double Rin, double Rout,
675 const Constants::StaveConfig& staveConfig,
676 const std::array<std::array<double, 3>, 4>& staveTriangles,
677 double z_offset_to_carbon_face,
678 std::vector<PosNegPositionTypes>& y_positionsPosNeg, unsigned& staveVolumeCount)
679{
680 auto& ft3Params = o2::ft3::FT3BaseParam::Instance();
681 // declare vector with number of 2xn sensor stacks (modules) -- only used for logging
682 // each entry is a vector, where each entry is the number of modules of that stack height
683 std::vector<std::vector<unsigned>> nSensorStackCountPerStave(
684 staveConfig.x_midpoints.size(),
685 std::vector<unsigned>(Constants::kSensorsPerStack.size(), 0));
686 std::vector<unsigned> nSensorStackTotal(Constants::kSensorsPerStack.size(), 0);
687 for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) {
688 y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}});
689 const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size());
690
691 double y_midpoint = 0.;
692 bool mirrorStaveAroundX = false;
693 // default positive and negative starting points has a gap around x-axis for symmetry
694 double stave_half_length = staveConfig.y_lengths[i_stave] / 2;
695 /*
696 * Have a gap around y=0, so sensors are not placed there.
697 * This means the stave is perfectly mirrored around the x-axis.
698 */
699 PositionRangeType y_ranges = {{Constants::stackGap / 2, stave_half_length},
700 {-Constants::stackGap / 2, -stave_half_length}};
701 if (staveMidpointAndMirror(staveConfig, staveID, y_midpoint, mirrorStaveAroundX)) {
702 // there is a defined midpoint for this stave, use this for starting points
703 y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length};
704 y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length};
705 }
706
707 // Define tolerances for cutting staves and placing sensors
708 double tolerance_inner, tolerance_outer;
709 if (staveConfig.isML) {
710 tolerance_inner = ft3Params.staveTolMLInner;
711 tolerance_outer = ft3Params.staveTolMLOuter;
712 } else {
713 tolerance_inner = ft3Params.staveTolOTInner;
714 tolerance_outer = ft3Params.staveTolOTOuter;
715 }
716 // cut staves on nominal inner radius if specified
717 if (tolerance_inner > staveConfig.maxToleranceInner) {
718 tolerance_inner = staveConfig.maxToleranceInner;
719 }
720 if (tolerance_outer > staveConfig.maxToleranceOuter) {
721 tolerance_outer = staveConfig.maxToleranceOuter;
722 }
723
724 /*
725 * There are two cases in which we want to mirror the stave around the x-axis,
726 * which correspond to the stave not going fully from + to - Rout in y.
727 *
728 * (1) The inner tolerance is 0 (or negative)
729 * a) AND either x_left or x_right lies within the inner radius
730 * (2) The inner tolerance is large enough to allow stave placement as wished
731 * a) AND the given stave midpoint is above the inner radius
732 */
733 double x_left_stave = staveConfig.x_midpoints[i_stave] - Constants::staveWidth / 2;
734 double x_right_stave = x_left_stave + Constants::staveWidth;
735 std::pair<double, double> absAllowedYRange =
736 calculate_y_range(x_left_stave, x_right_stave, Rin, Rout);
737
738 /*
739 * Shift allowed range by tolerance. Note that both values in the range must
740 * be non-negative, and if the inner is not, then set it to 0. This just means
741 * that there is no lower limit. The upper limit must however be larger than 0,
742 * if it is not, then skip this stave and give a warning.
743 */
744 absAllowedYRange.first -= tolerance_inner;
745 absAllowedYRange.second += tolerance_outer;
746
747 if (absAllowedYRange.first < 0) {
748 absAllowedYRange.first = 0;
749 }
750 if (absAllowedYRange.second <= 0) {
751 LOG(warning) << "For stave " << i_stave << " in layer " << layerNumber
752 << " with direction " << direction << ": no space to place sensors after applying tolerances, skipping stave.";
753 continue;
754 }
755
756 // Create the stave volumes and fill the y positions where to put sensors on the stave
757 add_stave_volumes(motherVolume, layerNumber, direction, staveConfig, i_stave,
758 staveTriangles, z_offset_to_carbon_face, absAllowedYRange,
759 y_midpoint, mirrorStaveAroundX, &staveVolumeCount);
760
761 // now add the sensor positions on the stave
762 for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) {
763 unsigned nModulesCurr = y_positionsPosNeg.back().first.size() + y_positionsPosNeg.back().second.size();
764 fill_stave_greedy(y_positionsPosNeg.back(), Constants::kSensorsPerStack[i_kSens],
765 y_ranges, absAllowedYRange);
766 unsigned nModulesAdded = y_positionsPosNeg.back().first.size() + y_positionsPosNeg.back().second.size() - nModulesCurr;
767 nSensorStackCountPerStave[i_stave][i_kSens] = nModulesAdded;
768 nSensorStackTotal[i_kSens] += nModulesAdded;
769 }
770 std::string moduleDebugStr = "Module size counts for layer " + std::to_string(layerNumber) + " in direction " + std::to_string(direction) + ":\n";
771 for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) {
772 moduleDebugStr += "\t" + std::to_string(nSensorStackCountPerStave[i_stave][i_kSens]) + " modules with " + std::to_string(Constants::kSensorsPerStack[i_kSens]) + " sensors stacked\n";
773 }
774 LOG(debug) << moduleDebugStr;
775 }
776 std::string totalModuleInfoStr =
777 "Total module size counts for layer " + std::to_string(layerNumber) +
778 " in direction " + std::to_string(direction) + ":\n";
779 for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) {
780 totalModuleInfoStr += "\t" + std::to_string(nSensorStackTotal[i_kSens]) + " modules with " + std::to_string(Constants::kSensorsPerStack[i_kSens]) + " sensors stacked\n";
781 }
782 LOG(info) << totalModuleInfoStr;
783}
784
785void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction,
786 double Rin, double Rout, double z_offset_local,
787 const Constants::StaveConfig& staveConfig,
788 TGeoVolume* motherVolume)
789{
790 LOG(debug) << "FT3Module: create_layout_staveGeo - Direction "
791 << direction << ", Layer " << layerNumber;
792
793 auto& ft3Params = o2::ft3::FT3BaseParam::Instance();
794
795 // First let's define some constants used throughout
796 /*
797 * we build the volume from the outside in, starting with the silicon,
798 * then glue & materials towards the stave. Depending on direction,
799 * the distance from the center will be mirrored.
800 *
801 * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE |
802 * ----------------------------------------------------------------> z
803 *
804 * Naturally, this will be mirrored for layers in the backwards direction,
805 * such that the face of the sensors always face the interaction region.
806 *
807 * Currently, we stipulate that the default stave face is at local z=0,
808 * that is then shifted by the half air thickness encapsulating the layer
809 * to avoid overlaps with the air and services. All offsets are
810 * calculated for backward direction (since that is a positive shift),
811 * and then flipped for forward. At that point, the innermost/frontmost
812 * stave face is at the edge of the air volume, so we shift it back a little
813 * to make space for the sensor materials and a slight margin.
814 */
815 double totalSensorMaterialThickness =
818 double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1;
819 double z_offset_to_glue_Ka =
820 z_offset_to_carbon_face + Constants::epoxyThickness / 2;
821 double z_offset_to_kapton =
822 z_offset_to_carbon_face + Constants::epoxyThickness +
824 double z_offset_to_copper =
825 z_offset_to_carbon_face + Constants::epoxyThickness +
827 double z_offset_to_glue_Si =
828 z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness +
830 double z_offset_to_silicon =
831 z_offset_to_carbon_face + Constants::epoxyThickness +
834
835 // initialise all y_positions, vector over all staves/columns
836 std::vector<PosNegPositionTypes> y_positionsPosNeg;
837 // stave triangle cross sections are the same for every stave (direction based)
838 std::array<std::array<double, 3>, 4> staveTriangles = buildStaveTriangle(direction);
839 unsigned staveVolumeCount = 0;
840 /*
841 * Either read the module positions out of the tabulated layout, or work them
842 * out here from the layer radii and the stave length. Both create the stave
843 * volumes and leave the module positions in y_positionsPosNeg, so the sensor
844 * placement below does not care which of the two ran.
845 *
846 * A disc without a tabulated layout falls back to the greedy fill even when
847 * the parameter is set, so that the discs which do have one can use it.
848 */
849 if (ft3Params.useExactStavePlacement && !staveConfig.exactStaveFills.empty()) {
850 build_staves_exact(motherVolume, layerNumber, direction, staveConfig, staveTriangles,
851 z_offset_to_carbon_face, y_positionsPosNeg, staveVolumeCount);
852 } else {
853 build_staves_greedy(motherVolume, layerNumber, direction, Rin, Rout, staveConfig,
854 staveTriangles, z_offset_to_carbon_face, y_positionsPosNeg,
855 staveVolumeCount);
856 }
857
858 // Create volumes for the sensors and the support materials on top of the stave
859 for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) {
860 double x_mid = staveConfig.x_midpoints[i_stave];
861 int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size());
862 /*
863 * Declare an offset multiplier for the z offsets, used for distinguishing
864 * sensors facing either forward or backward.
865 *
866 * In the stave layout, all sensors face inward, and isFront
867 * refers to whether a stave is shifted backwards or not. Thus,
868 * we decide the offset multiplier only with direction, to
869 * keep the face facing inwards.
870 */
871 bool isFront;
872 if (direction == 1) { // direction = 1 is forward
873 isFront = staveConfig.staveOnFront[i_stave];
874 } else {
875 isFront = !(staveConfig.staveOnFront[i_stave]);
876 }
877 int z_offset_multiplier = (direction == 1) ? -1 : 1;
878
879 // Get whether the stave is shifted for staggering or not
880 double z_stave_shift = 0;
881 if (!staveConfig.staveOnFront[i_stave]) {
882 // in forward direction, shifting backwards means +z shift
883 z_stave_shift = (direction == 1) ? Constants::z_offsetStave(staveConfig.x_midpoint_spacing)
885 }
886
887 unsigned sensor_count = 0; // reset for each stave
888 for (int y_sign = -1; y_sign < 2; y_sign += 2) {
889 // place sensors at positive and negative y
890 // recall for exact placement the first entry is filled, second is empty
891 // (this has no effect on the placement as it loops over all positions)
892 const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first
893 : y_positionsPosNeg[i_stave].second;
894 // define starting midpoint: y = y_start +- distance to middle of sensor
895 for (unsigned i_y_pos = 0; i_y_pos < positions.size(); i_y_pos++) {
896 double y_mid = positions[i_y_pos].first + y_sign * Constants::sensor2x1_height / 2;
897 for (unsigned i_sens = 0; i_sens < positions[i_y_pos].second; i_sens++) {
898 TGeoVolume* sensor;
899 // ------------ (1) Silicon sensor ------------
900 // left single sensor of the 2x1: place right edge half of sensor gap from center
901 double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift;
902 addSingleSensorVolume(
903 motherVolume, layerNumber, direction, i_stave, sensor_count,
905 y_mid, z_mid, true);
906 // right single sensor of the 2x1: place left edge half of sensor gap from center
907 addSingleSensorVolume(
908 motherVolume, layerNumber, direction, i_stave, sensor_count + 1,
910 y_mid, z_mid, false);
911 // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------
912 z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift;
913 add2x1GlueVolume(
914 motherVolume, layerNumber, direction, i_stave, sensor_count,
915 x_mid, y_mid, z_mid, "SiCu");
916 // ------------ (3) Copper layer (FPC) ------------
917 z_mid = z_offset_to_copper * z_offset_multiplier + z_stave_shift;
918 add2x1CopperVolume(
919 motherVolume, layerNumber, direction, i_stave, sensor_count,
920 x_mid, y_mid, z_mid);
921 // ------------ (4) Kapton layer (FPC) ------------
922 z_mid = z_offset_to_kapton * z_offset_multiplier + z_stave_shift;
923 add2x1KaptonVolume(
924 motherVolume, layerNumber, direction, i_stave, sensor_count,
925 x_mid, y_mid, z_mid);
926 // ------------ (5) Epoxy glue layer between stave and Kapton ------------
927 z_mid = z_offset_to_glue_Ka * z_offset_multiplier + z_stave_shift;
928 add2x1GlueVolume(
929 motherVolume, layerNumber, direction, i_stave, sensor_count,
930 x_mid, y_mid, z_mid, "CarbonKapton");
931 // increment to next sensor: (height + gap of one sensor)
933 sensor_count += 2; // same count for each material in the glued stack of materials
934 } // sensors in stack
935 } // for y_sign (writing of positive or negative y positions)
936 } // i_y_pos
937 } // i_stave
938}
939
940void 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)
941{
942
943 LOG(debug) << "FT3Module: create_layout - Layer " << layerNumber << ", Direction " << direction << ", Face " << face;
944 TGeoManager* geoManager = gGeoManager;
945
946 TGeoMedium* siliconMed = getMedium(Materials::MaterialID::Silicon);
947 TGeoMedium* copperMed = getMedium(Materials::MaterialID::Copper);
948 TGeoMedium* kaptonMed = getMedium(Materials::MaterialID::Kapton);
949 TGeoMedium* epoxyMed = getMedium(Materials::MaterialID::Epoxy);
950 TGeoMedium* AluminumMed = getMedium(Materials::MaterialID::Aluminum);
951
952 // double sensor_width = 2.5;
953 // double sensor_height = 9.6;
954 // double active_width = 2.3;
955 // double active_height = 9.6;
956
957 double sensor_width = 5.0;
958 double sensor_height = 9.6;
959 double inactive_width = 0.2; // per side
960 double active_width = 4.6;
961 double active_height = 9.6;
962
963 double silicon_thickness = 0.01;
964 double copper_thickness = 0.006;
965 double kapton_thickness = 0.03;
966 double epoxy_thickness = 0.0012;
967
968 double carbonFiberThickness = 0.01;
969
970 double foamSpacingThickness = 1.0;
971
972 int dist_offset = 0;
973
974 double x_offset;
975 double y_offset;
976
977 double z_offset = (face == "front") ? -foamSpacingThickness / 2.0 - carbonFiberThickness : foamSpacingThickness / 2.0 + carbonFiberThickness;
978
979 // offset correction
980 if (sensor_height == 3.2 && sensor_width == 2.5) {
981 x_offset = 0.8;
982 y_offset = 1.5;
983 } else if (sensor_height == 19.2 && sensor_width == 5) {
984 x_offset = 0.7;
985 y_offset = 9;
986 } else {
987 x_offset = sensor_width / 2;
988 y_offset = sensor_height / 2;
989 }
990
991 double x_condition_min = 0;
992 double x_condition_max = 0;
993 double offset_Rin_lower = 0;
994 double offset_Rin_upper = 0;
995 bool adjust_bottom_y_pos = false;
996 bool adjust_bottom_y_neg = false;
997 double x_adjust_bottom_y_pos = 0;
998 double bottom_y_pos_value = 0;
999 double bottom_y_neg_value = 0;
1000
1001 double Rin_offset = (sensor_height == 19.2) ? 1 : 0;
1002 double Rout_offset = (sensor_height == 19.2) ? 1 : 0;
1003
1004 if (Rin == 7 && sensor_height == 9.6 && sensor_width == 5) {
1005 x_condition_min = -Rin - 2;
1006 x_condition_max = Rin;
1007 dist_offset = 2;
1008 adjust_bottom_y_pos = true;
1009 adjust_bottom_y_neg = true;
1010 x_adjust_bottom_y_pos = 3.5;
1011 bottom_y_pos_value = 3.5;
1012 bottom_y_neg_value = -3.5;
1013 } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 5) {
1014 x_condition_min = -Rin - 6;
1015 x_condition_max = Rin;
1016 adjust_bottom_y_pos = true;
1017 adjust_bottom_y_neg = true;
1018 x_adjust_bottom_y_pos = 3.5;
1019 bottom_y_pos_value = 3.5;
1020 bottom_y_neg_value = -3.5;
1021 } else if ((Rin == 5 || Rin == 7) && sensor_height == 19.2) {
1022 x_condition_min = -Rin - 3;
1023 x_condition_max = Rin - 0.2;
1024 dist_offset = 2;
1025 adjust_bottom_y_pos = false;
1026 adjust_bottom_y_neg = false;
1027 } else if (Rin == 5 && sensor_height == 3.2) {
1028 x_condition_min = -(Rin + 2.6);
1029 x_condition_max = Rin + 1.5;
1030 adjust_bottom_y_pos = true;
1031 adjust_bottom_y_neg = true;
1032 x_adjust_bottom_y_pos = 3.5;
1033 bottom_y_pos_value = 3.5;
1034 bottom_y_neg_value = -3.5;
1035 } else if (Rin == 7 && sensor_height == 3.2) {
1036 x_condition_min = -Rin - 1;
1037 x_condition_max = Rin - 0.2;
1038 adjust_bottom_y_pos = true;
1039 adjust_bottom_y_neg = true;
1040 x_adjust_bottom_y_pos = 3.5;
1041 bottom_y_pos_value = 3.5;
1042 bottom_y_neg_value = -3.5;
1043 } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 2.5) {
1044 x_condition_min = -(Rin + 2.6);
1045 x_condition_max = Rin;
1046 adjust_bottom_y_pos = true;
1047 adjust_bottom_y_neg = true;
1048 x_adjust_bottom_y_pos = 3.5;
1049 bottom_y_pos_value = 3.5;
1050 bottom_y_neg_value = -3.5;
1051 } else if (Rin == 7 && sensor_height == 9.6 && sensor_width == 2.5) {
1052 x_condition_min = -Rin - 2.6;
1053 x_condition_max = Rin + 1;
1054 dist_offset = 2;
1055 adjust_bottom_y_pos = true;
1056 adjust_bottom_y_neg = true;
1057 x_adjust_bottom_y_pos = 5.5;
1058 bottom_y_pos_value = 3.5;
1059 bottom_y_neg_value = -3.5;
1060 } else if (Rin == 10 && sensor_height == 9.6 && sensor_width == 5.0) {
1061 x_condition_min = -Rin - 4;
1062 x_condition_max = Rin;
1063 dist_offset = 2;
1064 adjust_bottom_y_pos = false;
1065 adjust_bottom_y_neg = false;
1066 x_adjust_bottom_y_pos = 3.5;
1067 bottom_y_pos_value = 3.5;
1068 bottom_y_neg_value = -3.5;
1069 } else if (Rin == 20 && sensor_height == 9.6 && sensor_width == 5.0) {
1070 x_condition_min = -Rin - 4;
1071 x_condition_max = Rin;
1072 dist_offset = 2;
1073 adjust_bottom_y_pos = false;
1074 adjust_bottom_y_neg = false;
1075 x_adjust_bottom_y_pos = 3.5;
1076 bottom_y_pos_value = 3.5;
1077 bottom_y_neg_value = -3.5;
1078 } else {
1079 LOG(warning) << "Different config - to determine offsets needed for " << "Rin = " << Rin << " ; sensor_height = " << sensor_height << " ; sensor_width = " << sensor_width << " layer " << layerNumber;
1080 x_condition_min = -Rin - sensor_width;
1081 x_condition_max = Rin;
1082 adjust_bottom_y_pos = false;
1083 adjust_bottom_y_neg = false;
1084 }
1085
1086 offset_Rin_lower = Rin - Rin_offset;
1087 offset_Rin_upper = Rout + Rout_offset;
1088
1089 std::set<std::pair<double, double>> placed_sensors;
1090 int sensor_count = 0;
1091
1092 int placementCounter = 0;
1093 bool justSkipped = false;
1094
1095 std::vector<double> X_positions;
1096 std::vector<int> justSkipped1;
1097
1098 if (sensor_width == 2.5) {
1099 // logic for placement - x positions with complete overlap
1100 if (face == "front") {
1101 X_positions = {-63.4, -60.9, -54.2, -51.7, -45.0, -42.5, -35.8, -33.3, -26.6, -24.1, -17.4, -14.9,
1102 -8.2, -5.7, 1.0, 3.5, 10.2, 12.7, 19.4, 21.9, 28.6, 31.1, 37.8, 40.3, 47.0, 49.5,
1103 56.2, 58.7, 65.4};
1104 justSkipped1 = {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
1105 } else if (face == "back") {
1106 X_positions = {-65.5, -58.8, -56.3, -49.6, -47.1, -40.4, -37.9, -31.2, -28.7, -22.0, -19.5, -12.8,
1107 -10.3, -3.6, -1.1, 5.6, 8.1, 14.8, 17.3, 24.0, 26.5, 33.2, 35.7, 42.4, 44.9,
1108 51.6, 54.1, 60.8, 63.3};
1109 justSkipped1 = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0};
1110 }
1111 } else {
1112 if (Rin == 10 || Rin == 20) { // v3 paving, rough attempt
1113 float overlap = 0.3;
1114 // NB: these are left edges
1115 float X_start = -2.0 - 13.5 * (sensor_width - overlap);
1116 float X_start_pos = 2.0 - 0.5 * (sensor_width - overlap);
1117 if (face == "back") {
1118 X_start += (sensor_width - overlap);
1119 X_start_pos += (sensor_width - overlap);
1120 }
1121 while (X_start < -2) {
1122 X_positions.push_back(X_start);
1123 justSkipped1.push_back(1);
1124 X_start += 2 * (sensor_width - overlap);
1125 }
1126 while (X_start_pos < Rout + x_offset - sensor_width) {
1127 X_positions.push_back(X_start_pos);
1128 justSkipped1.push_back(1);
1129 X_start_pos += 2 * (sensor_width - overlap);
1130 }
1131 } else {
1132 // filling for sensors with 2x width, each row skipped
1133 if (face == "front") {
1134 X_positions = {-63.4, -54.2, -45, -35.8, -26.6, -17.4, -8.2, 1., 10.2, 19.4, 28.6, 37.8, 47., 56.2, 65.4};
1135 justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
1136 } else if (face == "back") {
1137 X_positions = {-58.8, -49.6, -40.4, -31.2, -22, -12.8, -3.6, 5.6, 14.8, 24, 33.2, 42.4, 51.6, 60.8};
1138 justSkipped1 = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
1139 }
1140 }
1141 }
1142
1143 if (layout_type == "rectangular") {
1144
1145 double x_start = -Rout;
1146 double x_end = Rout;
1147
1148 std::vector<double> x_positions;
1149 for (double x = x_start; x <= x_end; x += sensor_width) {
1150 x_positions.push_back(x);
1151 }
1152
1153 int rowCounter = 0;
1154 const int rowsToAlternate = 2;
1155
1156 for (size_t i = 0; i < X_positions.size(); ++i) {
1157
1158 double x = X_positions[i];
1159 bool justSkippedValue = justSkipped1[i];
1160
1161 std::vector<double> y_positions_positive;
1162 std::vector<double> y_positions_negative;
1163
1164 for (double y = -Rout - Rin_offset; y <= Rout + Rin_offset; y += sensor_height) {
1165 std::vector<std::pair<double, double>> corners = {
1166 {x, y},
1167 {x + sensor_width, y},
1168 {x, y + sensor_height},
1169 {x + sensor_width, y + sensor_height}};
1170
1171 bool within_bounds = std::all_of(corners.begin(), corners.end(), [&](const std::pair<double, double>& corner) {
1172 double cx = corner.first;
1173 double cy = corner.second;
1174 return (offset_Rin_lower <= std::sqrt(cx * cx + cy * cy) && std::sqrt(cx * cx + cy * cy) <= offset_Rin_upper);
1175 });
1176
1177 if (within_bounds) {
1178 if (y >= 0) {
1179 y_positions_positive.push_back(y);
1180 } else {
1181 y_positions_negative.push_back(y);
1182 }
1183 }
1184 }
1185
1186 // adjust y positions near inner circle for positive y
1187 if (x_condition_min <= x && x <= x_condition_max && !y_positions_positive.empty()) {
1188 double first_y_pos = y_positions_positive.front();
1189 double last_y_pos = y_positions_positive.back() - sensor_height;
1190 double top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1191 double bottom_y_pos = std::max(calculate_y_circle(x, Rin), calculate_y_circle(x + sensor_width, Rin));
1192 double top_distance_pos = top_y_pos - last_y_pos;
1193
1194 if (adjust_bottom_y_pos && x > x_adjust_bottom_y_pos) {
1195 bottom_y_pos = bottom_y_pos_value;
1196 }
1197
1198 double bottom_distance_pos = first_y_pos - bottom_y_pos;
1199
1200 if (std::abs(top_distance_pos + bottom_distance_pos) >= sensor_height) {
1201 for (auto& y : y_positions_positive) {
1202 y -= bottom_distance_pos - 0.2;
1203 }
1204 y_positions_positive.push_back(y_positions_positive.back() + sensor_height);
1205 }
1206 }
1207
1208 // adjust y positions near inner circle for negative y
1209 if (x_condition_min <= x && x <= x_condition_max && !y_positions_negative.empty()) {
1210 double first_y_neg = y_positions_negative.front();
1211 double last_y_neg = y_positions_negative.back() + sensor_height;
1212 double top_y_neg = -std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1213 double bottom_y_neg = -std::max(calculate_y_circle(x, Rin), calculate_y_circle(x + sensor_width, Rin));
1214 double top_distance_neg = -(top_y_neg - first_y_neg);
1215
1216 if (adjust_bottom_y_neg && x > x_adjust_bottom_y_pos) {
1217 bottom_y_neg = bottom_y_neg_value;
1218 }
1219
1220 double bottom_distance_neg = -(last_y_neg - bottom_y_neg);
1221
1222 top_distance_neg = std::abs(top_distance_neg);
1223 bottom_distance_neg = std::abs(bottom_distance_neg);
1224 std::sort(y_positions_negative.begin(), y_positions_negative.end());
1225
1226 if (std::abs(top_distance_neg + bottom_distance_neg) >= sensor_height) {
1227 if (sensor_height == 19.2) {
1228 for (auto& y : y_positions_negative) {
1229 y -= bottom_distance_neg;
1230 }
1231 } else {
1232 for (auto& y : y_positions_negative) {
1233 y += bottom_distance_neg - 0.2;
1234 }
1235 }
1236 y_positions_negative.push_back(y_positions_negative.front() - sensor_height);
1237 }
1238 }
1239
1240 // adjust positions for the rest of the disk
1241 if ((x < x_condition_min || x > x_condition_max) && !y_positions_negative.empty() && !y_positions_positive.empty()) {
1242 double first_y_neg = y_positions_negative.front();
1243 double last_y_pos = y_positions_positive.back() + sensor_height;
1244 double top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1245 double bottom_y_pos = -top_y_pos;
1246
1247 double top_distance_pos = std::abs(top_y_pos - last_y_pos);
1248 double bottom_distance_pos = std::abs(first_y_neg - bottom_y_pos);
1249
1250 if (top_distance_pos + bottom_distance_pos >= sensor_height) {
1251 for (auto& y : y_positions_positive) {
1252 y += top_distance_pos - 0.2;
1253 }
1254 for (auto& y : y_positions_negative) {
1255 y += top_distance_pos - 0.2;
1256 }
1257 double new_y = y_positions_negative.front() - sensor_height;
1258
1259 if (static_cast<int>(new_y) > static_cast<int>(bottom_y_pos)) {
1260 y_positions_negative.push_back(new_y);
1261 }
1262 }
1263
1264 // Make symmetric adjustments
1265 std::sort(y_positions_negative.begin(), y_positions_negative.end());
1266 std::sort(y_positions_positive.begin(), y_positions_positive.end());
1267
1268 double first_y_pos = y_positions_negative.front();
1269
1270 last_y_pos = y_positions_positive.back() + sensor_height;
1271
1272 top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout));
1273 bottom_y_pos = -top_y_pos;
1274 top_distance_pos = std::abs(top_y_pos - last_y_pos);
1275 bottom_distance_pos = std::abs(first_y_pos - bottom_y_pos);
1276
1277 double Lb = (bottom_distance_pos + top_distance_pos) / 2;
1278
1279 if (top_distance_pos < Lb) {
1280 double shift = Lb - top_distance_pos;
1281 for (auto& y : y_positions_negative) {
1282 y -= shift;
1283 }
1284 for (auto& y : y_positions_positive) {
1285 y -= shift;
1286 }
1287 } else if (top_distance_pos > Lb) {
1288 double shift = top_distance_pos - Lb;
1289 for (auto& y : y_positions_negative) {
1290 y += shift;
1291 }
1292 for (auto& y : y_positions_positive) {
1293 y += shift;
1294 }
1295 }
1296 }
1297
1298 std::vector<double> y_positions = y_positions_positive;
1299 y_positions.insert(y_positions.end(), y_positions_negative.begin(), y_positions_negative.end());
1300
1301 for (double y : y_positions) {
1302
1303 int SiColor;
1304 double R_material_threshold = 0;
1305
1306 if (placed_sensors.find({x, y}) == placed_sensors.end()) {
1307 placed_sensors.insert({x, y});
1308 TGeoVolume* sensor;
1309
1310 double inactive_width = (sensor_width - active_width) / 2;
1311 double left_inactive_x_shift;
1312 double right_inactive_x_shift;
1313 double active_x_shift_sensor;
1314
1315 if (face == "front") {
1316
1317 double active_x_shift, inactive_x_shift;
1318
1319 if (justSkippedValue) {
1320 active_x_shift = x + inactive_width / 2;
1321 active_x_shift_sensor = active_x_shift + inactive_width;
1322
1323 inactive_x_shift = x - active_width / 2 + inactive_width / 2;
1324 } else {
1325 active_x_shift = x - inactive_width / 2;
1326 active_x_shift_sensor = active_x_shift - inactive_width;
1327
1328 inactive_x_shift = x + active_width / 2 - inactive_width / 2;
1329 }
1330
1331 double inactive_x_shift_left, inactive_x_shift_right;
1332
1333 if (sensor_width == 5.0) {
1334
1335 inactive_x_shift_left = x - sensor_width / 2 + inactive_width;
1336 inactive_x_shift_right = x + sensor_width / 2;
1337 }
1338
1339 std::vector<std::pair<double, double>> corners_shifted = {
1340 {x, y},
1341 {x + sensor_width, y},
1342 {x, y + sensor_height},
1343 {x + sensor_width, y + sensor_height}};
1344
1345 bool within_bounds = true;
1346 for (const auto& corner : corners_shifted) {
1347 double cx = corner.first;
1348 double cy = corner.second;
1349 double dist = std::sqrt(cx * cx + cy * cy);
1350
1351 if (Rin > dist || dist >= Rout) {
1352 within_bounds = false;
1353 break;
1354 }
1355 }
1356
1357 if (within_bounds) {
1358
1359 double r_squared = (x + x_offset) * (x + x_offset) + (y + y_offset) * (y + y_offset);
1360
1361 if (r_squared < R_material_threshold * R_material_threshold) {
1362 silicon_thickness = 0.005;
1363 copper_thickness = 0.00475;
1364 kapton_thickness = 0.03;
1365 epoxy_thickness = 0.0012;
1366
1367 SiColor = kOrange;
1368 } else {
1369 silicon_thickness = 0.01;
1370 copper_thickness = 0.006;
1371 kapton_thickness = 0.03;
1372 epoxy_thickness = 0.0012;
1373
1374 SiColor = kGreen;
1375 }
1376
1377 if (sensor_width == 2.5) {
1378 // silicon
1379 std::string sensor_name = "FT3Sensor_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1380 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, active_height / 2, silicon_thickness / 2);
1381 sensor->SetLineColor(SiColor);
1382 sensor->SetFillColorAlpha(SiColor, 0.4);
1383 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift_sensor + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1384
1385 std::string inactive_name = "FT3inactive_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1386 sensor = geoManager->MakeBox(inactive_name.c_str(), siliconMed, (sensor_width - active_width) / 2, sensor_height / 2, silicon_thickness / 2);
1387 sensor->SetLineColor(kRed);
1388 sensor->SetFillColorAlpha(kRed, 1.0);
1389 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1390
1391 } else {
1392
1393 std::string sensor_name = "FT3Sensor_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1394 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, sensor_height / 2, silicon_thickness / 2);
1395 sensor->SetLineColor(SiColor);
1396 sensor->SetFillColorAlpha(SiColor, 0.4);
1397 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + x + inactive_width / 2, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1398
1399 std::string inactive_name_left = "FT3inactive_left_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1400 sensor = geoManager->MakeBox(inactive_name_left.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1401 sensor->SetLineColor(kRed);
1402 sensor->SetFillColorAlpha(kRed, 1.0);
1403 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_left, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1404
1405 std::string inactive_name_right = "FT3inactive_right_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1406 sensor = geoManager->MakeBox(inactive_name_right.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1407 sensor->SetLineColor(kRed);
1408 sensor->SetFillColorAlpha(kRed, 1.0);
1409 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_right, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2));
1410 }
1411
1412 // silicon-to-FPC epoxy glue
1413 std::string glue_up_name = "FT3glue_up_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1414 sensor = geoManager->MakeBox(glue_up_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1415 sensor->SetLineColor(kBlue);
1416 sensor->SetFillColorAlpha(kBlue, 1.0);
1417 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness / 2));
1418
1419 if (r_squared < R_material_threshold * R_material_threshold) {
1420 std::string alu_name = "FT3aluminum_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1421 sensor = geoManager->MakeBox(alu_name.c_str(), AluminumMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1422 sensor->SetLineColor(kBlack);
1423 sensor->SetFillColorAlpha(kBlack, 0.4);
1424 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness / 2));
1425
1426 } else {
1427 std::string copper_name = "FT3copper_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1428 sensor = geoManager->MakeBox(copper_name.c_str(), copperMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1429 sensor->SetLineColor(kBlack);
1430 sensor->SetFillColorAlpha(kBlack, 0.4);
1431 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness / 2));
1432 }
1433
1434 // kapton
1435 std::string fpc_name = "FT3fpc_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1436 sensor = geoManager->MakeBox(fpc_name.c_str(), kaptonMed, sensor_width / 2, sensor_height / 2, kapton_thickness / 2);
1437 sensor->SetLineColor(kGreen);
1438 sensor->SetFillColorAlpha(kGreen, 0.4);
1439 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness / 2));
1440
1441 // FPC-to-support epoxy glue
1442 std::string glue_down_name = "FT3glue_down_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1443 sensor = geoManager->MakeBox(glue_down_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1444 sensor->SetLineColor(kBlue);
1445 sensor->SetFillColorAlpha(kBlue, 1.0);
1446 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness / 2));
1447 }
1448 } else {
1449 double x_shifted = x;
1450 double inactive_x_shift, active_x_shift;
1451 double active_x_shift_sensor;
1452
1453 if (justSkippedValue) {
1454 active_x_shift = x + inactive_width / 2;
1455 active_x_shift_sensor = active_x_shift + inactive_width;
1456
1457 inactive_x_shift = x - active_width / 2 + inactive_width / 2;
1458 } else {
1459 active_x_shift = x - inactive_width / 2;
1460 active_x_shift_sensor = active_x_shift - inactive_width;
1461
1462 inactive_x_shift = x + active_width / 2 - inactive_width / 2;
1463 }
1464
1465 double inactive_x_shift_left, inactive_x_shift_right;
1466
1467 if (sensor_width == 5.0) {
1468
1469 inactive_x_shift_left = x - sensor_width / 2 + inactive_width;
1470 inactive_x_shift_right = x + sensor_width / 2;
1471 }
1472
1473 std::vector<std::pair<double, double>> corners_shifted = {
1474 {x_shifted, y},
1475 {x_shifted + sensor_width, y},
1476 {x_shifted, y + sensor_height},
1477 {x_shifted + sensor_width, y + sensor_height}};
1478
1479 bool within_bounds = true;
1480 for (const auto& corner : corners_shifted) {
1481 double cx = corner.first;
1482 double cy = corner.second;
1483 double dist = std::sqrt(cx * cx + cy * cy);
1484
1485 if (Rin > dist + dist_offset || dist >= Rout) {
1486 within_bounds = false;
1487 break;
1488 }
1489 }
1490
1491 if (within_bounds) {
1492
1493 double r_squared = (x + x_offset) * (x + x_offset) + (y + y_offset) * (y + y_offset);
1494
1495 if (r_squared < R_material_threshold * R_material_threshold) {
1496 silicon_thickness = 0.005;
1497 copper_thickness = 0.00475; // thinner -> + replaced by alu
1498 kapton_thickness = 0.03;
1499 epoxy_thickness = 0.0006;
1500
1501 SiColor = kOrange;
1502 } else {
1503 silicon_thickness = 0.01;
1504 copper_thickness = 0.006;
1505 kapton_thickness = 0.03;
1506 epoxy_thickness = 0.0012;
1507
1508 SiColor = kGreen;
1509 }
1510
1511 // FPC-to-support epoxy glue
1512 std::string glue_down_name = "FT3glue_down_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1513 sensor = geoManager->MakeBox(glue_down_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1514 sensor->SetLineColor(kBlue);
1515 sensor->SetFillColorAlpha(kBlue, 1.0);
1516 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness / 2));
1517
1518 // Kapton
1519 std::string fpc_name = "FT3fpc_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1520 sensor = geoManager->MakeBox(fpc_name.c_str(), kaptonMed, sensor_width / 2, sensor_height / 2, kapton_thickness / 2);
1521 sensor->SetLineColor(kGreen);
1522 sensor->SetFillColorAlpha(kGreen, 0.4);
1523 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness / 2));
1524
1525 if (r_squared < R_material_threshold * R_material_threshold) {
1526 // replace copper with alu
1527 std::string alu_name = "FT3aluminum_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1528 sensor = geoManager->MakeBox(alu_name.c_str(), AluminumMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1529 sensor->SetLineColor(kBlack);
1530 sensor->SetFillColorAlpha(kBlack, 0.4);
1531 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness / 2));
1532
1533 } else {
1534 std::string copper_name = "FT3copper_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1535 sensor = geoManager->MakeBox(copper_name.c_str(), copperMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2);
1536 sensor->SetLineColor(kBlack);
1537 sensor->SetFillColorAlpha(kBlack, 0.4);
1538 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness / 2));
1539 }
1540
1541 // silicon-to-FPC epoxy glue
1542 std::string glue_up_name = "FT3glue_up_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1543 sensor = geoManager->MakeBox(glue_up_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2);
1544 sensor->SetLineColor(kBlue);
1545 sensor->SetFillColorAlpha(kBlue, 1.0);
1546 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness / 2));
1547
1548 if (sensor_width == 2.5) {
1549
1550 std::string sensor_name = "FT3Sensor_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1551 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, active_height / 2, silicon_thickness / 2);
1552 sensor->SetLineColor(SiColor);
1553 sensor->SetFillColorAlpha(SiColor, 0.4);
1554 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift_sensor + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1555
1556 std::string inactive_name = "FT3inactive_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1557 sensor = geoManager->MakeBox(inactive_name.c_str(), siliconMed, (sensor_width - active_width) / 2, sensor_height / 2, silicon_thickness / 2);
1558 sensor->SetLineColor(kRed);
1559 sensor->SetFillColorAlpha(kRed, 1.0);
1560 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1561
1562 } else {
1563 // active (4.6 cm centered)
1564 std::string sensor_name = "FT3Sensor_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1565 sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, sensor_height / 2, silicon_thickness / 2);
1566 sensor->SetLineColor(SiColor);
1567 sensor->SetFillColorAlpha(SiColor, 0.4);
1568 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + x_shifted + inactive_width / 2, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1569
1570 // left inactive strip
1571 std::string inactive_name_left = "FT3inactive_left_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1572 sensor = geoManager->MakeBox(inactive_name_left.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1573 sensor->SetLineColor(kRed);
1574 sensor->SetFillColorAlpha(kRed, 1.0);
1575 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_left, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1576
1577 // right inactive strip
1578 std::string inactive_name_right = "FT3inactive_right_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count);
1579 sensor = geoManager->MakeBox(inactive_name_right.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2);
1580 sensor->SetLineColor(kRed);
1581 sensor->SetFillColorAlpha(kRed, 1.0);
1582 motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_right, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2));
1583 }
1584 }
1585 }
1586 }
1587 }
1588
1589 rowCounter++;
1590 }
1591 }
1592 LOG(debug) << "FT3Module: done create_layout";
1593}
1594
1595void FT3Module::createModule(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume)
1596{
1597
1598 LOG(debug) << "FT3Module: createModule - Layer " << layerNumber << ", Direction " << direction << ", Face " << face;
1599 create_layout(mZ, layerNumber, direction, Rin, Rout, overlap, face, layout_type, motherVolume);
1600 LOG(debug) << "FT3Module: done createModule";
1601}
1602
1603void FT3Module::createModule_staveGeo(double mZ, int layerNumber, int direction,
1604 double Rin, double Rout, double z_offset_local,
1605 const Constants::StaveConfig& staveConfig,
1606 TGeoVolume* motherVolume)
1607{
1608 LOG(debug) << "FT3Module: createModule_staveGeo - Layer " << layerNumber
1609 << " at z=" << mZ << ", Direction " << direction;
1610 create_layout_staveGeo(mZ, layerNumber, direction, Rin, Rout,
1611 z_offset_local, staveConfig, motherVolume);
1612 LOG(debug) << "FT3Module: done createModule_staveGeo";
1613}
std::ostringstream debug
std::pair< double, double > calculate_y_range(double x_left, double x_right, double Rin, double Rout)
Definition FT3Module.cxx:40
double calculate_y_circle(double x, double radius)
Definition FT3Module.cxx:35
std::array< std::array< double, 3 >, 4 > buildStaveTriangle(int direction)
bool staveMidpointAndMirror(const Constants::StaveConfig &staveConfig, int staveID, double &y_midpoint, bool &mirrorStaveAroundX)
Definition of the FT3Module class.
std::pair< std::pair< double, double >, std::pair< double, double > > PositionRangeType
Definition FT3Module.h:31
std::vector< PositionType > PositionTypes
Definition FT3Module.h:28
std::pair< PositionTypes, PositionTypes > PosNegPositionTypes
Definition FT3Module.h:29
int32_t i
uint32_t c
Definition RawData.h:2
void createModule_staveGeo(double mZ, int layerNumber, int direction, double Rin, double Rout, double z_offset_local, const Constants::StaveConfig &staveConfig, 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)
GLint GLenum GLint x
Definition glcorearb.h:403
GLsizeiptr size
Definition glcorearb.h:659
GLuint color
Definition glcorearb.h:1272
GLuint const GLchar * name
Definition glcorearb.h:781
GLint first
Definition glcorearb.h:399
GLenum GLuint GLint GLenum face
Definition glcorearb.h:3184
GLint y
Definition glcorearb.h:270
GLboolean * data
Definition glcorearb.h:298
const std::unordered_map< MaterialID, MaterialProperties > materials
const int SiInactiveColor
const double effectiveCarbonThickness_Stave
const double z_offsetStave(double x_midpoint_spacing)
const EosCardParams & getEosCardParams(bool isML)
const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc)
const double getStackHeight(unsigned nSensorsPerStack)
const std::vector< unsigned > kSensorsPerStack
TGeoMedium * getMedium(Materials::MaterialID id)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
const std::vector< double > & x_midpoints
const std::vector< double > & y_lengths
const std::vector< std::vector< StaveFill > > & exactStaveFills
const std::map< int, std::pair< double, bool > > & staveID_to_y_midpoint
const std::vector< bool > & staveOnFront
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"