Project
Loading...
Searching...
No Matches
VDGeometryBuilder.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
13
14#include <TGeoVolume.h>
15#include <TGeoMatrix.h>
16#include <TGeoTube.h>
17#include <TGeoBBox.h>
18#include <TMath.h>
19#include <TGeoCompositeShape.h>
20#include <TString.h>
22
23#include "TGeoManager.h"
24
25#include "Framework/Logger.h"
29
30namespace o2::trk
31{
32
33static std::vector<VDSensorDesc> gVDSensors; // stays in this TU only
34std::vector<VDSensorDesc>& vdSensorRegistry() { return gVDSensors; }
35
36void clearVDSensorRegistry() { gVDSensors.clear(); }
37
38void registerSensor(const std::string& volName, int petal, VDSensorDesc::Region region, VDSensorDesc::Type type, int idx)
39{
40 gVDSensors.push_back({volName, petal, region, type, idx});
41}
42
43static inline std::string makeSensorName(const std::string& layerName, int layerNumber)
44{
45 return Form("%s_%s%d", layerName.c_str(), o2::trk::GeometryTGeo::getTRKSensorPattern(), layerNumber);
46}
47
48namespace
49{
50
51// Config: which volumes count as SOLIDS to subtract from the vacuum volume
52inline bool isSolidToCut(const TGeoVolume* v)
53{
54 const char* nm = v->GetName();
55 const char* med = v->GetMedium() ? v->GetMedium()->GetName() : "";
56 // silicon sensors (barrel + disks)
57 if (med && strcmp(med, "TRK_SILICON$") == 0) {
58 return true;
59 }
60 // walls, sidewalls, cold-plate, service rings (names from your builders)
61 if (TString(nm).BeginsWith("VD_InnerWallArc")) {
62 return true;
63 }
64 if (TString(nm).BeginsWith("VD_OuterWallArc")) {
65 return true;
66 }
67 if (TString(nm).BeginsWith("VD_SideWall")) {
68 return true;
69 }
70 if (TString(nm).Contains("_Coldplate")) {
71 return true;
72 }
73 if (TString(nm).BeginsWith("IRIS_Service_Neg")) {
74 return true;
75 }
76 if (TString(nm).BeginsWith("IRIS_Service_Pos_InVac")) {
77 return true;
78 }
79 return false;
80}
81
82// Ensure every leaf shape has a stable, informative name
83inline const char* ensureShapeName(TGeoVolume* v)
84{
85 auto* sh = v->GetShape();
86 TString nm = sh->GetName();
87 if (nm.IsNull() || nm.BeginsWith("TGeo")) {
88 TString wanted = TString(v->GetName()) + "_sh";
89 // avoid collisions
90 int k = 0;
91 TString cand = wanted;
92 auto* shapes = gGeoManager ? gGeoManager->GetListOfShapes() : nullptr;
93 while (shapes && shapes->FindObject(cand)) {
94 cand = Form("%s_%d", wanted.Data(), ++k);
95 }
96 sh->SetName(cand);
97 if (shapes && !shapes->FindObject(cand)) {
98 shapes->Add(sh);
99 }
100 }
101 return sh->GetName();
102}
103
104// Recorder state for the petal-local composite
105static TString gPetalSolidsFormula;
106static int gLocalTrIdx = 0;
107
108// add "ShapeName:IRIS_LOC_TR_k" to the petal-local formula (no outer rotation)
109inline void appendLocalTerm(const char* shapeName, const TGeoHMatrix& H)
110{
111 auto* ct = new TGeoCombiTrans(H);
112 ct->SetName(Form("IRIS_LOC_TR_%d", gLocalTrIdx++));
113 ct->RegisterYourself();
114 if (!gPetalSolidsFormula.IsNull()) {
115 gPetalSolidsFormula += "+";
116 }
117 gPetalSolidsFormula += TString::Format("%s:%s", shapeName, ct->GetName());
118}
119
120// DFS: compose LOCAL transforms only (identity prefix), to capture the petal contents
121void traversePetalLocal(TGeoVolume* vol, const TGeoHMatrix& prefix)
122{
123 auto* nodes = vol->GetNodes();
124 if (!nodes) {
125 return;
126 }
127 for (int i = 0; i < nodes->GetEntriesFast(); ++i) {
128 auto* node = (TGeoNode*)nodes->At(i);
129 auto* childV = node->GetVolume();
130 TGeoHMatrix H(prefix);
131 if (auto* m = node->GetMatrix()) {
132 H.Multiply(m);
133 }
134
135 if (isSolidToCut(childV)) {
136 const char* shapeName = ensureShapeName(childV);
137 appendLocalTerm(shapeName, H);
138 }
139 traversePetalLocal(childV, H);
140 }
141}
142
143// Build (once) a petal-local composite containing ONLY solids (walls, silicon, coldplate, services, disks)
144inline void buildPetalSolidsComposite(TGeoVolume* petalAsm)
145{
146 // If it already exists, skip
147 if (gGeoManager && gGeoManager->GetListOfShapes() && gGeoManager->GetListOfShapes()->FindObject("IRIS_PETAL_SOLIDSsh")) {
148 return;
149 }
150
151 gPetalSolidsFormula.Clear();
152 gLocalTrIdx = 0;
153
154 TGeoHMatrix I; // identity
155 traversePetalLocal(petalAsm, I);
156
157 if (gPetalSolidsFormula.IsNull()) {
158 LOGP(error, "IRIS_PETAL_SOLIDSsh formula is empty; did not find solids in petal.");
159 return;
160 }
161
162 LOGP(info, "IRIS_PETAL_SOLIDSsh formula: {}", gPetalSolidsFormula.Data());
163 new TGeoCompositeShape("IRIS_PETAL_SOLIDSsh", gPetalSolidsFormula.Data());
164}
165
166// Build the global cutout by rotating the petal-local composite n times with (p+0.5) phase
167inline void buildIrisCutoutFromPetalSolid(int nPetals)
168{
169 // Create n rotation transforms
170 TString cutFormula;
171 for (int p = 0; p < nPetals; ++p) {
172 const double phi = (360.0 / nPetals) * (p + 0.5);
173 auto* R = new TGeoRotation();
174 R->RotateZ(phi);
175 auto* RT = new TGeoCombiTrans(0, 0, 0, R);
176 RT->SetName(Form("IRIS_PETAL_ROT_%d", p));
177 RT->RegisterYourself();
178 if (p) {
179 cutFormula += "+";
180 }
181 cutFormula += Form("IRIS_PETAL_SOLIDSsh:%s", RT->GetName());
182 }
183 LOGP(info, "IRIS_CUTOUTsh formula: {}", cutFormula.Data());
184 new TGeoCompositeShape("IRIS_CUTOUTsh", cutFormula.Data());
185
186 // --- Sanity check: required matrices & shapes exist
187 auto* mats = gGeoManager ? gGeoManager->GetListOfMatrices() : nullptr;
188 auto* shps = gGeoManager ? gGeoManager->GetListOfShapes() : nullptr;
189
190 if (!mats || !shps) {
191 LOGP(error, "IRIS cutout sanity: gGeoManager not initialized properly (mats/shapes missing).");
192 } else {
193 bool ok = true;
194
195 // Check the petal rotations were registered and referenced
196 for (int p = 0; p < nPetals; ++p) {
197 const TString name = Form("IRIS_PETAL_ROT_%d", p);
198 if (!mats->FindObject(name)) {
199 LOGP(error, "IRIS cutout sanity: missing matrix {}", name.Data());
200 ok = false;
201 }
202 }
203
204 // Check that the local petal composite exists
205 if (!shps->FindObject("IRIS_PETAL_SOLIDSsh")) {
206 LOGP(error, "IRIS cutout sanity: shape 'IRIS_PETAL_SOLIDSsh' not found.");
207 ok = false;
208 }
209
210 // Check that the global cutout shape was created
211 if (!shps->FindObject("IRIS_CUTOUTsh")) {
212 LOGP(error, "IRIS cutout sanity: shape 'IRIS_CUTOUTsh' not found.");
213 ok = false;
214 }
215
216 if (ok) {
217 LOGP(info, "IRIS cutout sanity: OK ({} petals).", nPetals);
218 }
219 }
220}
221
222} // namespace
223
224// =================== Specs & constants (ROOT units: cm) ===================
225static constexpr double kX2X0 = 0.001f; // 0.1% X0 per layer
226static constexpr double kLenZ_cm = 50.0f; // L0/L1/L2 Z length
227
228// Radii (cm)
229static constexpr double rL0_cm = 0.5f; // 5 mm
230static constexpr double rL1_cm = 1.2f; // 12 mm
231static constexpr double rL2_cm = 2.5f; // 25 mm
232
233// IRIS5 rectangular L0 width (cm)
234static constexpr double kL0RectHeight_cm = 0.5f; // 5.0 mm
235static constexpr double kL0RectWidth_cm = 0.83f; // 8.3 mm
236
237// Disks radii (cm)
238static constexpr double diskRin_cm = 0.5f; // 5 mm
239static constexpr double diskRout_cm = 2.5f; // 25 mm
240static const double diskZ_cm[6] = {-34.0f, -30.0f, -26.0f, 26.0f, 30.0f, 34.0f};
241
242// Petal walls specifications (cm)
243static constexpr double kPetalZ_cm = 70.0f; // full wall height
244static constexpr double kWallThick_cm = 0.015f; // 0.15 mm
245static constexpr double kInnerWallRadius_cm = 0.48f; // 4.8 mm (ALWAYS cylindrical)
246static constexpr double kOuterWallRadius_cm = 3.0f; // 30 mm (can be changed)
247static constexpr double kEps_cm = 1.e-4f;
248
249// Coldplate specs (cm)
250static constexpr double kColdplateRadius_cm = 2.6f; // 26 mm (outer radius)
251static constexpr double kColdplateThickness_cm = 0.15f; // 1.5 mm
252static constexpr double kColdplateZ_cm = 50.0f; // full length
253
254// ========== φ-span helpers (gap/arc → degrees) ==========
255namespace
256{
257
258// Convert a linear gap at radius R into an angular gap (deg)
259inline double degFromArc(double arc, double radius)
260{
261 // arc and radius in the SAME units (cm or mm); result in degrees
262 return (radius > 0.f) ? (arc / radius) * TMath::RadToDeg() : 0.f;
263}
264
272inline double phiSpanFromGap(int nPetals, double gap, double radius)
273{
274 if (nPetals <= 0 || radius <= 0.f) {
275 return 0.f;
276 }
277 const double petalPhiDeg = 360.f / nPetals;
278 const double phi = petalPhiDeg - degFromArc(gap, radius);
279 return phi > 0.f ? phi : 0.f;
280}
281
286inline double phiSpanFromArc(double arcLen, double radius)
287{
288 return (arcLen > 0.f && radius > 0.f) ? degFromArc(arcLen, radius) : 0.f;
289}
290
291inline TGeoCombiTrans rotZ(double phiDeg)
292{
293 auto* r = new TGeoRotation();
294 r->RotateZ(static_cast<Double_t>(phiDeg));
295 return TGeoCombiTrans(0., 0., 0., r);
296}
297} // namespace
298
299// ============ Petal sub-builders (LOCAL coords only, no rotation) =========
300
301// Walls: inner cylindrical arc at r=4.8 mm (always), outer arc wall, and two side plates.
302static void addPetalWalls(TGeoVolume* petalAsm, int nPetals, double outerRadius_cm = kOuterWallRadius_cm)
303{
304 if (!petalAsm) {
305 LOGP(error, "addPetalWalls: petalAsm is null");
306 return;
307 }
308
310 const TGeoMedium* med = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_ALUMINIUM5083");
311
312 if (!med) {
313 LOGP(warning, "Petal walls: ALICE3_TRKSERVICES_ALUMINIUM5083$ not found, walls not created.");
314 return;
315 }
316
317 const double halfPhi = 0.5f * (360.f / static_cast<double>(nPetals));
318 const double halfZ = 0.5f * kPetalZ_cm;
319
320 // ---- Inner cylindrical wall (always at r=4.8 mm) ----
321 {
322 auto* s = new TGeoTubeSeg(static_cast<Double_t>(kInnerWallRadius_cm),
323 static_cast<Double_t>(kInnerWallRadius_cm + kWallThick_cm),
324 static_cast<Double_t>(halfZ),
325 static_cast<Double_t>(-halfPhi),
326 static_cast<Double_t>(+halfPhi));
327 auto* v = new TGeoVolume("VD_InnerWallArc", s, med);
328 v->SetLineColor(kGray + 2);
329 v->SetTransparency(70);
330 petalAsm->AddNode(v, 1);
331 }
332
333 // ---- Outer arc wall ----
334 {
335 auto* s = new TGeoTubeSeg(static_cast<Double_t>(outerRadius_cm),
336 static_cast<Double_t>(outerRadius_cm + kWallThick_cm),
337 static_cast<Double_t>(halfZ),
338 static_cast<Double_t>(-halfPhi),
339 static_cast<Double_t>(+halfPhi));
340 auto* v = new TGeoVolume("VD_OuterWallArc", s, med);
341 v->SetLineColor(kGray + 2);
342 v->SetTransparency(70);
343 petalAsm->AddNode(v, 1);
344 }
345
346 // ---- Side walls (boxes) at ±halfPhi ----
347 const double radialLen = (outerRadius_cm - (kInnerWallRadius_cm + kWallThick_cm));
348 auto* sideS = new TGeoBBox(static_cast<Double_t>(0.5f * radialLen),
349 static_cast<Double_t>(0.5f * kWallThick_cm),
350 static_cast<Double_t>(halfZ));
351 auto* sideV = new TGeoVolume("VD_SideWall", sideS, med);
352 sideV->SetLineColor(kGray + 2);
353 sideV->SetTransparency(70);
354
355 for (int sgn : {-1, +1}) {
356 const double phi = sgn * halfPhi;
357 const double rMid = kInnerWallRadius_cm + kWallThick_cm + 0.5f * radialLen;
358 const double rad = static_cast<double>(TMath::DegToRad());
359 const double x = rMid * std::cos(phi * rad);
360 const double y = rMid * std::sin(phi * rad);
361 auto* rot = new TGeoRotation();
362 rot->RotateZ(static_cast<Double_t>(phi));
363 auto* tr = new TGeoCombiTrans(static_cast<Double_t>(x),
364 static_cast<Double_t>(y),
365 0.0, rot);
366 petalAsm->AddNode(sideV, (sgn < 0 ? 1 : 2), tr);
367 }
368}
369
370// Build inner layers (L0..L2). L0 may be rectangular (IRIS5) or cylindrical.
371// φ-spans derive from spec gaps/arc; all local placement (no rotation).
372static void addBarrelLayers(TGeoVolume* petalAsm, int nPetals, int petalID, bool rectangularL0)
373{
374 if (!petalAsm) {
375 LOGP(error, "addBarrelLayers: petalAsm is null");
376 return;
377 }
378
379 // Per spec (mm → cm)
380 constexpr double gapL0_cm = 0.163f; // 1.63 mm
381 constexpr double gapL1L2_cm = 0.12f; // 1.2 mm
382 constexpr double arcL0_cm = 0.6247f; // 6.247 mm
383
384 // φ spans
385 const double phiL0_deg = phiSpanFromGap(nPetals, gapL0_cm, rL0_cm); // L0 gap-defined
386 const double phiL1_deg = phiSpanFromGap(nPetals, gapL1L2_cm, rL1_cm); // L1 gap-defined
387 const double phiL2_deg = phiSpanFromGap(nPetals, gapL1L2_cm, rL2_cm); // L2 gap-defined
388
389 const std::string nameL0 =
390 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
392
393 if (rectangularL0) {
394 VDRectangularLayer L0(0,
395 nameL0,
396 kX2X0, kL0RectWidth_cm, kLenZ_cm, kLenZ_cm);
397
398 // Correct translation: move to radius + half width along x
399 double x = kL0RectHeight_cm + L0.getChipThickness() / 2.;
400 LOGP(info, "Placing rectangular L0 at r={:.3f} cm (half-width={:.3f} cm)", x, 0.5f * kL0RectWidth_cm);
401 double y = 0.0;
402 double z = 0.0;
403
404 // Correct rotation: rotate 90 degrees around z so long side is horizontal
405 auto* rot = new TGeoRotation();
406 rot->RotateZ(90.0);
407
408 auto* tr = new TGeoCombiTrans(x, y, z, rot);
409 L0.createLayer(petalAsm, tr);
410 registerSensor(makeSensorName(nameL0, 0), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Plane, /*idx*/ 0);
411 } else {
412 VDCylindricalLayer L0(0,
413 nameL0,
414 kX2X0, rL0_cm, phiL0_deg, kLenZ_cm, kLenZ_cm);
415 L0.createLayer(petalAsm, nullptr);
416 registerSensor(makeSensorName(nameL0, 0), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 0);
417 }
418
419 const std::string nameL1 =
420 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
422
423 VDCylindricalLayer L1(1,
424 nameL1,
425 kX2X0, rL1_cm, phiL1_deg, kLenZ_cm, kLenZ_cm);
426 L1.createLayer(petalAsm, nullptr);
427 registerSensor(makeSensorName(nameL1, 1), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 1);
428
429 const std::string nameL2 =
430 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
432
433 VDCylindricalLayer L2(2,
434 nameL2,
435 kX2X0, rL2_cm, phiL2_deg, kLenZ_cm, kLenZ_cm);
436 L2.createLayer(petalAsm, nullptr);
437 registerSensor(makeSensorName(nameL2, 2), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 2);
438}
439
440// Build cold plate (cylindrical) in local coordinates, and add it to the petal assembly.
441static void addColdPlate(TGeoVolume* petalAsm, int nPetals, int petalId)
442{
443 if (!petalAsm) {
444 LOGP(error, "addColdPlate: petalAsm is null");
445 return;
446 }
447
448 // Resolve medium: prefer provided medium, otherwise try to fetch from geo manager
449 const TGeoMedium* med = gGeoManager->GetMedium("ALICE3_TRKSERVICES_CERAMIC");
450 if (!med) {
451 LOGP(error, "addColdPlate: can't find the medium.");
452 }
453
454 // Angular span for one petal (deg)
455 constexpr double gapL1L2_cm = 0.12f; // 1.2 mm
456
457 // φ spans
458 const double phiSpanColdplate_deg = phiSpanFromGap(nPetals, gapL1L2_cm, rL2_cm); // L2 gap-defined
459 const double halfPhiDeg = 0.5f * phiSpanColdplate_deg;
460 const double startPhi = -halfPhiDeg;
461 const double endPhi = +halfPhiDeg;
462
463 // Build tube segment: inner radius, outer radius = inner + thickness, half-length Z
464 auto* shape = new TGeoTubeSeg(static_cast<Double_t>(kColdplateRadius_cm),
465 static_cast<Double_t>(kColdplateRadius_cm + kColdplateThickness_cm),
466 static_cast<Double_t>(0.5 * kColdplateZ_cm),
467 static_cast<Double_t>(startPhi),
468 static_cast<Double_t>(endPhi));
469
470 TString volName = TString::Format("Petal%d_Coldplate", petalId);
471 auto* coldVol = new TGeoVolume(volName, shape, med);
472 coldVol->SetLineColor(kAzure - 3);
473 coldVol->SetTransparency(10);
474
475 // Place in local petal coordinates (no extra transform); keep object alive by allocating shape/volume on heap.
476 petalAsm->AddNode(coldVol, 1);
477
478 LOGP(info, "Adding cold plate {} r={:.3f} cm t={:.3f} cm Lz={:.3f} cm φ=[{:.3f}, {:.3f}]",
479 volName.Data(), kColdplateRadius_cm, kColdplateThickness_cm, kColdplateZ_cm, startPhi, endPhi);
480}
481
482// Add IRIS service module(s) as aluminum annular cylinders placed outside the petals.
483// The two modules are placed at z = ±(36 + halfLength).
484static void addIRISServiceModules(TGeoVolume* petalAsm, int nPetals)
485{
486 if (!petalAsm) {
487 LOGP(error, "addIRISServiceModules: petalAsm is null");
488 return;
489 }
490
491 auto* matAl = new TGeoMaterial("ALUMINUM", 26.9815, 13, 2.70);
492 const TGeoMedium* med = new TGeoMedium("ALUMINUM", 4, matAl);
493
494 if (!med) {
495 LOGP(error, "addIRISServiceModules: ALUMINUM medium not found.");
496 return;
497 }
498
499 constexpr double radius = 3.2; // cm (inner radius)
500 constexpr double thickness = 0.133; // cm (radial thickness)
501 constexpr double halfLength = 19.5; // cm (half-length along Z)
502 const double rIn = radius;
503 const double rOut = radius + thickness;
504
505 // Petal angular span. If you have an exact half-φ from your walls, use it here.
506 const double halfPhi_deg = 0.5 * (360.0 / double(nPetals));
507
508 // Create shape once and reuse
509 auto* segSh = new TGeoTubeSeg(
510 "IRIS_SERVICE_SEGsh",
511 rIn, rOut,
512 halfLength,
513 -halfPhi_deg, halfPhi_deg);
514
515 // Positive Z module
516 TString namePos = "IRIS_Service_Pos";
517 auto* volPos = new TGeoVolume(namePos, segSh, med);
518 volPos->SetLineColor(kRed + 2);
519 volPos->SetTransparency(50);
520
521 // Negative Z module: reuse same shape object, give different name
522 TString nameNeg = "IRIS_Service_Neg";
523 auto* volNeg = new TGeoVolume(nameNeg, segSh, med);
524 volNeg->SetLineColor(kRed + 2);
525 volNeg->SetTransparency(50);
526
527 // Translations (heap-allocated so ROOT keeps them)
528 const double zpos = 36.0 + halfLength;
529 auto* transPos = new TGeoTranslation(0.0, 0.0, static_cast<Double_t>(zpos));
530 auto* transNeg = new TGeoTranslation(0.0, 0.0, static_cast<Double_t>(-zpos));
531
532 // Add to mother volume
533 petalAsm->AddNode(volPos, 1, transPos);
534 petalAsm->AddNode(volNeg, 2, transNeg);
535
536 LOGP(info, "Added IRIS service modules at z = ±{} cm, r=[{}, {}] cm", zpos, rIn, rOut);
537}
538
539// Only the A-side "inside vacuum" piece participates in the cutout.
540static void addIRISServiceModulesSegmented(TGeoVolume* petalAsm, int nPetals)
541{
542 if (!petalAsm) {
543 LOGP(error, "addIRISServiceModulesSegmented: petalAsm is null");
544 return;
545 }
546
547 // --- Service geometry (same as your previous values)
548 constexpr double rIn = 3.2; // cm
549 constexpr double thickness = 0.133; // cm
550 constexpr double rOut = rIn + thickness;
551 constexpr double halfLen = 19.5; // cm
552 constexpr double z0 = 36.0 + halfLen; // 55.5 cm center of +Z service
553 const double zMinA = z0 - halfLen; // 36.0 cm
554 const double zMaxA = z0 + halfLen; // 75.0 cm
555
556 // --- Vacuum vessel window around z∈[-L/2, +L/2] with wall thickness on +Z side
557 // Keep these in sync with TRKServices::createVacuumCompositeShape()
558 constexpr double vacuumVesselLength = 76.0; // cm
559 constexpr double vacuumVesselThickness = 0.08; // cm (0.8 mm)
560 const double halfVess = 0.5 * vacuumVesselLength; // 38.0 cm
561 const double gapStart = halfVess; // 38.00
562 const double gapEnd = halfVess + vacuumVesselThickness; // 38.08
563
564 // --- Petal φ-span (segment)
565 const double halfPhi = 0.5 * (360.0 / double(nPetals));
566
567 auto* matAl = new TGeoMaterial("ALUMINUM", 26.9815, 13, 2.70);
568 const TGeoMedium* med = new TGeoMedium("ALUMINUM", 4, matAl);
569
570 if (!med) {
571 LOGP(error, "addIRISServiceModules: ALUMINUM medium not found.");
572 return;
573 }
574
575 // =========================
576 // C-side (negative Z) whole
577 // =========================
578 {
579 auto* sh = new TGeoTubeSeg(rIn, rOut, halfLen, -halfPhi, +halfPhi);
580 auto* vN = new TGeoVolume("IRIS_Service_Neg", sh, med);
581 vN->SetLineColor(kRed + 2);
582 vN->SetTransparency(55);
583 petalAsm->AddNode(vN, 1, new TGeoTranslation(0., 0., -(z0)));
584 }
585
586 // =====================================
587 // A-side (positive Z): split with a gap
588 // =====================================
589 // Piece 1 (INSIDE vacuum): z ∈ [zMinA, min(zMaxA, gapStart)] → goes into cutout
590 const double L_inVac = std::max(0.0, std::min(zMaxA, gapStart) - zMinA); // expected ~2.0 cm
591 if (L_inVac > 0) {
592 const double dz = 0.5 * L_inVac;
593 const double zc = zMinA + dz; // center of lower slice, ≈ 37.0 cm
594 auto* sh = new TGeoTubeSeg(rIn, rOut, dz, -halfPhi, halfPhi);
595 sh->SetName("IRIS_SERVICE_POS_INVACsh");
596 auto* vP = new TGeoVolume("IRIS_Service_Pos_InVac", sh, med);
597 vP->SetLineColor(kRed + 2);
598 vP->SetTransparency(55);
599 petalAsm->AddNode(vP, 1, new TGeoTranslation(0., 0., zc));
600 LOGP(info, "IRIS A-side (InVac): z=[{:.3f},{:.3f}] cm, len={:.3f} cm",
601 zc - dz, zc + dz, 2 * dz);
602 } else {
603 LOGP(warning, "IRIS A-side (InVac): no overlap with vacuum (L_inVac<=0)");
604 }
605
606 // Gap (no material): (gapStart, gapEnd) = (38.00, 38.08)
607
608 // Piece 2 (OUT of vacuum): z ∈ [max(zMinA, gapEnd), zMaxA] → NOT in cutout
609 const double L_outVac = std::max(0.0, zMaxA - std::max(zMinA, gapEnd)); // expected ~36.92 cm
610 if (L_outVac > 0) {
611 const double dz = 0.5 * L_outVac;
612 const double zc = std::max(zMinA, gapEnd) + dz; // center of upper slice
613 auto* sh = new TGeoTubeSeg(rIn, rOut, dz, -halfPhi, +halfPhi);
614 sh->SetName("IRIS_SERVICE_POS_OUTVACsh");
615 auto* vP = new TGeoVolume("IRIS_Service_Pos_OutVac", sh, med);
616 vP->SetLineColor(kRed + 1);
617 vP->SetTransparency(70);
618 petalAsm->AddNode(vP, 2, new TGeoTranslation(0., 0., +zc));
619 LOGP(info, "IRIS A-side (OutVac): z=[{:.3f},{:.3f}] cm, len={:.3f} cm",
620 zc - dz, zc + dz, 2 * dz);
621 } else {
622 LOGP(warning, "IRIS A-side (OutVac): no upper piece (L_outVac<=0)");
623 }
624}
625
626// Build disks in local coords: each disk gets only a local Z translation.
627// φ span from gap at rOut.
628static void addDisks(TGeoVolume* petalAsm, int nPetals, int petalID)
629{
630
631 if (!petalAsm) {
632 LOGP(error, "addDisks: petalAsm is null");
633 return;
634 }
635
636 const double phiDisk_deg = phiSpanFromGap(nPetals, 2 * kWallThick_cm, diskRin_cm);
637
638 for (int i = 0; i < 6; ++i) {
639 const std::string nameD =
640 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
642
643 VDDiskLayer disk(i,
644 nameD,
645 kX2X0, diskRin_cm, diskRout_cm, phiDisk_deg, diskZ_cm[i]);
646
647 // Local Z placement only
648 auto* tr = new TGeoTranslation(0.0, 0.0, static_cast<Double_t>(disk.getZPosition()));
649 disk.createLayer(petalAsm, tr);
650 registerSensor(makeSensorName(nameD, i), petalID, VDSensorDesc::Region::Disk, VDSensorDesc::Type::Plane, /*idx*/ i);
651 }
652}
653
654// Build one complete petal assembly (walls + L0..L2 + disks) in LOCAL coords.
655static TGeoVolume* buildPetalAssembly(int nPetals, int petalID, bool rectangularL0)
656{
657 auto* petalAsm = new TGeoVolumeAssembly(Form("PETAL_%d", petalID));
658 addPetalWalls(petalAsm, nPetals, kOuterWallRadius_cm);
659
660 // Pass petalID to layers/disks for naming
661 addBarrelLayers(petalAsm, nPetals, petalID, rectangularL0);
662 addColdPlate(petalAsm, nPetals, petalID);
663 addDisks(petalAsm, nPetals, petalID);
664 addIRISServiceModulesSegmented(petalAsm, nPetals);
665
666 return petalAsm;
667}
668
669// =================== Public entry points ===================
670
671void createIRIS4Geometry(TGeoVolume* motherVolume)
672{
673 if (!motherVolume) {
674 LOGP(error, "createIRIS4Geometry: motherVolume is null");
675 return;
676 }
677
679
680 constexpr int nPetals = 4;
681 for (int p = 0; p < nPetals; ++p) {
682 auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ false);
683 // Build the petal-local solids composite once from the FIRST petal
684 if (p == 0) {
685 buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords
686 }
687 const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5);
688 auto* R = new TGeoRotation();
689 R->RotateZ(phiDeg);
690 auto* T = new TGeoCombiTrans(0, 0, 0, R);
691 motherVolume->AddNode(petal, p + 1, T);
692 }
693 buildIrisCutoutFromPetalSolid(nPetals);
694}
695
696void createIRIS5Geometry(TGeoVolume* motherVolume)
697{
698 if (!motherVolume) {
699 LOGP(error, "createIRIS5Geometry: motherVolume is null");
700 return;
701 }
702
704
705 constexpr int nPetals = 4;
706 for (int p = 0; p < nPetals; ++p) {
707 auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ true);
708 // Build the petal-local solids composite once from the FIRST petal
709 if (p == 0) {
710 buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords
711 }
712 const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5);
713 auto* R = new TGeoRotation();
714 R->RotateZ(phiDeg);
715 auto* T = new TGeoCombiTrans(0, 0, 0, R);
716 motherVolume->AddNode(petal, p + 1, T);
717 }
718 buildIrisCutoutFromPetalSolid(nPetals);
719}
720
721void createIRIS4aGeometry(TGeoVolume* motherVolume)
722{
723 if (!motherVolume) {
724 LOGP(error, "createIRIS4aGeometry: motherVolume is null");
725 return;
726 }
727
729
730 constexpr int nPetals = 3;
731 for (int p = 0; p < nPetals; ++p) {
732 auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ false);
733 // Build the petal-local solids composite once from the FIRST petal
734 if (p == 0) {
735 buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords
736 }
737 const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5);
738 auto* R = new TGeoRotation();
739 R->RotateZ(phiDeg);
740 auto* T = new TGeoCombiTrans(0, 0, 0, R);
741 motherVolume->AddNode(petal, p + 1, T);
742 }
743 buildIrisCutoutFromPetalSolid(nPetals);
744}
745
746void createSinglePetalDebug(TGeoVolume* motherVolume, int petalID, int nPetals, bool rectangularL0)
747{
748 auto* petal = buildPetalAssembly(nPetals, petalID, rectangularL0);
749
750 // Optionally rotate the petal for display
751 const double phiDeg = (360.f / static_cast<double>(nPetals)) * (static_cast<double>(petalID) + 0.5f);
752 auto* R = new TGeoCombiTrans(0, 0, 0, new TGeoRotation("", phiDeg, 0, 0));
753 motherVolume->AddNode(petal, 1, R);
754
755 LOGP(info, "Debug: Added Petal{} to {}", petalID, motherVolume->GetName());
756}
757
758} // namespace o2::trk
std::unique_ptr< expressions::Node > node
int32_t i
static MaterialManager & Instance()
static const char * getTRKPetalDiskPattern()
static const char * getTRKSensorPattern()
static const char * getTRKPetalLayerPattern()
static const char * getTRKPetalPattern()
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLboolean r
Definition glcorearb.h:1233
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
constexpr double thickness
Definition Specs.h:37
std::vector< VDSensorDesc > & vdSensorRegistry()
void createSinglePetalDebug(TGeoVolume *motherVolume, int petalID=0, int nPetals=4, bool rectangularL0=false)
void createIRIS4aGeometry(TGeoVolume *motherVolume)
void createIRIS4Geometry(TGeoVolume *motherVolume)
void registerSensor(const std::string &volName, int petal, VDSensorDesc::Region region, VDSensorDesc::Type type, int idx)
void createIRIS5Geometry(TGeoVolume *motherVolume)
void clearVDSensorRegistry()
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52