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).BeginsWith("VD_InnerWallCyl")) {
71 return true;
72 }
73 if (TString(nm).BeginsWith("VD_OuterWallCyl")) {
74 return true;
75 }
76 if (TString(nm).Contains("_Coldplate")) {
77 return true;
78 }
79 if (TString(nm).BeginsWith("IRIS_Service_Neg")) {
80 return true;
81 }
82 if (TString(nm).BeginsWith("IRIS_Service_Pos_InVac")) {
83 return true;
84 }
85 return false;
86}
87
88// Ensure every leaf shape has a stable, informative name
89inline const char* ensureShapeName(TGeoVolume* v)
90{
91 auto* sh = v->GetShape();
92 TString nm = sh->GetName();
93 if (nm.IsNull() || nm.BeginsWith("TGeo")) {
94 TString wanted = TString(v->GetName()) + "_sh";
95 // avoid collisions
96 int k = 0;
97 TString cand = wanted;
98 auto* shapes = gGeoManager ? gGeoManager->GetListOfShapes() : nullptr;
99 while (shapes && shapes->FindObject(cand)) {
100 cand = Form("%s_%d", wanted.Data(), ++k);
101 }
102 sh->SetName(cand);
103 if (shapes && !shapes->FindObject(cand)) {
104 shapes->Add(sh);
105 }
106 }
107 return sh->GetName();
108}
109
110// Recorder state for the petal-local composite
111static TString gPetalSolidsFormula;
112static int gLocalTrIdx = 0;
113
114// add "ShapeName:IRIS_LOC_TR_k" to the petal-local formula (no outer rotation)
115inline void appendLocalTerm(const char* shapeName, const TGeoHMatrix& H)
116{
117 auto* ct = new TGeoCombiTrans(H);
118 ct->SetName(Form("IRIS_LOC_TR_%d", gLocalTrIdx++));
119 ct->RegisterYourself();
120 if (!gPetalSolidsFormula.IsNull()) {
121 gPetalSolidsFormula += "+";
122 }
123 gPetalSolidsFormula += TString::Format("%s:%s", shapeName, ct->GetName());
124}
125
126// DFS: compose LOCAL transforms only (identity prefix), to capture the petal contents
127void traversePetalLocal(TGeoVolume* vol, const TGeoHMatrix& prefix)
128{
129 auto* nodes = vol->GetNodes();
130 if (!nodes) {
131 return;
132 }
133 for (int i = 0; i < nodes->GetEntriesFast(); ++i) {
134 auto* node = (TGeoNode*)nodes->At(i);
135 auto* childV = node->GetVolume();
136 TGeoHMatrix H(prefix);
137 if (auto* m = node->GetMatrix()) {
138 H.Multiply(m);
139 }
140
141 if (isSolidToCut(childV)) {
142 const char* shapeName = ensureShapeName(childV);
143 appendLocalTerm(shapeName, H);
144 }
145 traversePetalLocal(childV, H);
146 }
147}
148
149// Build (once) a petal-local composite containing ONLY solids (walls, silicon, coldplate, services, disks)
150inline void buildPetalSolidsComposite(TGeoVolume* petalAsm)
151{
152 // If it already exists, skip
153 if (gGeoManager && gGeoManager->GetListOfShapes() && gGeoManager->GetListOfShapes()->FindObject("IRIS_PETAL_SOLIDSsh")) {
154 return;
155 }
156
157 gPetalSolidsFormula.Clear();
158 gLocalTrIdx = 0;
159
160 TGeoHMatrix I; // identity
161 traversePetalLocal(petalAsm, I);
162
163 if (gPetalSolidsFormula.IsNull()) {
164 LOGP(error, "IRIS_PETAL_SOLIDSsh formula is empty; did not find solids in petal.");
165 return;
166 }
167
168 LOGP(info, "IRIS_PETAL_SOLIDSsh formula: {}", gPetalSolidsFormula.Data());
169 new TGeoCompositeShape("IRIS_PETAL_SOLIDSsh", gPetalSolidsFormula.Data());
170}
171
172// Build the global cutout by rotating the petal-local composite n times with (p+0.5) phase
173inline void buildIrisCutoutFromPetalSolid(int nPetals)
174{
175 auto* shps = gGeoManager->GetListOfShapes();
176 auto* base = shps ? dynamic_cast<TGeoShape*>(shps->FindObject("IRIS_PETAL_SOLIDSsh")) : nullptr;
177 if (!base) {
178 LOGP(error, "IRIS cutout: shape 'IRIS_PETAL_SOLIDSsh' not found.");
179 return;
180 }
181
182 // IMPORTANT: for nPetals==1, a composite expression like "A:tr" is invalid.
183 // Just clone the petal solids shape as the global cutout.
184 if (nPetals == 1) {
185 // Remove any previous shape with same name if it exists (optional but keeps things clean)
186 if (shps->FindObject("IRIS_CUTOUTsh")) {
187 // ROOT shape lists are owned by gGeoManager; removing is not always necessary.
188 // Keeping it simple: just create a unique name if it already exists.
189 LOGP(warning, "IRIS cutout: 'IRIS_CUTOUTsh' already exists; overwriting by clone name reuse may be unsafe.");
190 }
191
192 auto* cut = dynamic_cast<TGeoShape*>(base->Clone("IRIS_CUTOUTsh"));
193 if (!cut) {
194 LOGP(error, "IRIS cutout: failed to clone 'IRIS_PETAL_SOLIDSsh' to 'IRIS_CUTOUTsh'.");
195 return;
196 }
197
198 LOGP(info, "IRIS_CUTOUTsh created as clone of IRIS_PETAL_SOLIDSsh (nPetals=1).");
199 return;
200 }
201
202 // nPetals > 1: build union of rotated copies
203 TString cutFormula;
204 for (int p = 0; p < nPetals; ++p) {
205 const double phi = (360.0 / nPetals) * (p + 0.5);
206 auto* R = new TGeoRotation();
207 R->RotateZ(phi);
208 auto* RT = new TGeoCombiTrans(0, 0, 0, R);
209 RT->SetName(Form("IRIS_PETAL_ROT_%d", p));
210 RT->RegisterYourself();
211
212 if (p) {
213 cutFormula += "+";
214 }
215 cutFormula += Form("IRIS_PETAL_SOLIDSsh:%s", RT->GetName());
216 }
217
218 LOGP(info, "IRIS_CUTOUTsh formula: {}", cutFormula.Data());
219 auto* cut = new TGeoCompositeShape("IRIS_CUTOUTsh", cutFormula.Data());
220 (void)cut;
221
222 // Stronger sanity: ensure it parsed into a boolean node
223 auto* cutCheck = dynamic_cast<TGeoCompositeShape*>(shps->FindObject("IRIS_CUTOUTsh"));
224 if (!cutCheck || !cutCheck->GetBoolNode()) {
225 LOGP(error, "IRIS cutout sanity: IRIS_CUTOUTsh exists but parsing failed (no BoolNode).");
226 } else {
227 LOGP(info, "IRIS cutout sanity: OK ({} petals).", nPetals);
228 }
229}
230
231} // namespace
232
233// =================== Specs & constants (ROOT units: cm) ===================
234static constexpr double kX2X0 = 0.001f; // 0.1% X0 per layer
235static constexpr double kLenZ_cm = 50.0f; // L0/L1/L2 Z length
236
237// Radii (cm)
238static constexpr double rL0_cm = 0.5f; // 5 mm
239static constexpr double rL1_cm = 1.2f; // 12 mm
240static constexpr double rL2_cm = 2.5f; // 25 mm
241
242// IRIS5 rectangular L0 width (cm)
243static constexpr double kL0RectHeight_cm = 0.5f; // 5.0 mm
244static constexpr double kL0RectWidth_cm = 0.83f; // 8.3 mm
245
246// Disks radii (cm)
247static constexpr double diskRin_cm = 0.5f; // 5 mm
248static constexpr double diskRout_cm = 2.5f; // 25 mm
249static const double diskZ_cm[6] = {-34.0f, -30.0f, -26.0f, 26.0f, 30.0f, 34.0f};
250
251// Petal walls specifications (cm)
252static constexpr double kPetalZ_cm = 70.0f; // full wall height
253static constexpr double kWallThick_cm = 0.015f; // 0.15 mm
254static constexpr double kInnerWallRadius_cm = 0.48f; // 4.8 mm (ALWAYS cylindrical)
255static constexpr double kOuterWallRadius_cm = 3.0f; // 30 mm (can be changed)
256static constexpr double kEps_cm = 1.e-4f;
257
258// Coldplate specs (cm)
259static constexpr double kColdplateRadius_cm = 2.6f; // 26 mm (outer radius)
260static constexpr double kColdplateThickness_cm = 0.15f; // 1.5 mm
261static constexpr double kColdplateZ_cm = 50.0f; // full length
262
263// ========== φ-span helpers (gap/arc → degrees) ==========
264namespace
265{
266
267// Convert a linear gap at radius R into an angular gap (deg)
268inline double degFromArc(double arc, double radius)
269{
270 // arc and radius in the SAME units (cm or mm); result in degrees
271 return (radius > 0.f) ? (arc / radius) * TMath::RadToDeg() : 0.f;
272}
273
281inline double phiSpanFromGap(int nPetals, double gap, double radius)
282{
283 if (nPetals <= 0 || radius <= 0.f) {
284 return 0.f;
285 }
286 const double petalPhiDeg = 360.f / nPetals;
287 const double phi = petalPhiDeg - degFromArc(gap, radius);
288 return phi > 0.f ? phi : 0.f;
289}
290
295inline double phiSpanFromArc(double arcLen, double radius)
296{
297 return (arcLen > 0.f && radius > 0.f) ? degFromArc(arcLen, radius) : 0.f;
298}
299
300inline TGeoCombiTrans rotZ(double phiDeg)
301{
302 auto* r = new TGeoRotation();
303 r->RotateZ(static_cast<Double_t>(phiDeg));
304 return TGeoCombiTrans(0., 0., 0., r);
305}
306} // namespace
307
308// ============ Petal sub-builders (LOCAL coords only, no rotation) =========
309
310// Walls: inner cylindrical arc at r=4.8 mm (always), outer arc wall, and two side plates.
311static void addPetalWalls(TGeoVolume* petalAsm,
312 int nPetals,
313 double outerRadius_cm = kOuterWallRadius_cm,
314 bool withSideWalls = true,
315 bool fullCylindricalRadialWalls = false)
316{
317 if (!petalAsm) {
318 LOGP(error, "addPetalWalls: petalAsm is null");
319 return;
320 }
321
323 const TGeoMedium* med = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_ALUMINIUM5083");
324
325 if (!med) {
326 LOGP(warning, "Petal walls: ALICE3_TRKSERVICES_ALUMINIUM5083$ not found, walls not created.");
327 return;
328 }
329
330 const double halfZ = 0.5 * kPetalZ_cm;
331
332 // In full-cylinder radial-wall mode we ignore nPetals for the radial walls.
333 const double halfPhi = fullCylindricalRadialWalls ? 180.0 : 0.5 * (360.0 / static_cast<double>(nPetals));
334
335 // ---- Inner radial wall ----
336 if (fullCylindricalRadialWalls) {
337 auto* s = new TGeoTube(static_cast<Double_t>(kInnerWallRadius_cm),
338 static_cast<Double_t>(kInnerWallRadius_cm + kWallThick_cm),
339 static_cast<Double_t>(halfZ));
340 auto* v = new TGeoVolume("VD_InnerWallCyl", s, med);
341 v->SetLineColor(kGray + 2);
342 v->SetTransparency(70);
343 petalAsm->AddNode(v, 1);
344 } else {
345 auto* s = new TGeoTubeSeg(static_cast<Double_t>(kInnerWallRadius_cm),
346 static_cast<Double_t>(kInnerWallRadius_cm + kWallThick_cm),
347 static_cast<Double_t>(halfZ),
348 static_cast<Double_t>(-halfPhi),
349 static_cast<Double_t>(+halfPhi));
350 auto* v = new TGeoVolume("VD_InnerWallArc", s, med);
351 v->SetLineColor(kGray + 2);
352 v->SetTransparency(70);
353 petalAsm->AddNode(v, 1);
354 }
355
356 // ---- Outer radial wall ----
357 if (fullCylindricalRadialWalls) {
358 auto* s = new TGeoTube(static_cast<Double_t>(outerRadius_cm),
359 static_cast<Double_t>(outerRadius_cm + kWallThick_cm),
360 static_cast<Double_t>(halfZ));
361 auto* v = new TGeoVolume("VD_OuterWallCyl", s, med);
362 v->SetLineColor(kGray + 2);
363 v->SetTransparency(70);
364 petalAsm->AddNode(v, 1);
365 } else {
366 auto* s = new TGeoTubeSeg(static_cast<Double_t>(outerRadius_cm),
367 static_cast<Double_t>(outerRadius_cm + kWallThick_cm),
368 static_cast<Double_t>(halfZ),
369 static_cast<Double_t>(-halfPhi),
370 static_cast<Double_t>(+halfPhi));
371 auto* v = new TGeoVolume("VD_OuterWallArc", s, med);
372 v->SetLineColor(kGray + 2);
373 v->SetTransparency(70);
374 petalAsm->AddNode(v, 1);
375 }
376
377 // ---- Side plates (skip in "single petal full cylinders" mode) ----
378 if (!withSideWalls) {
379 return;
380 }
381
382 // ---- Side walls (boxes) at ±halfPhi ----
383 const double radialLen = (outerRadius_cm - (kInnerWallRadius_cm + kWallThick_cm));
384 auto* sideS = new TGeoBBox(static_cast<Double_t>(0.5f * radialLen),
385 static_cast<Double_t>(0.5f * kWallThick_cm),
386 static_cast<Double_t>(halfZ));
387 auto* sideV = new TGeoVolume("VD_SideWall", sideS, med);
388 sideV->SetLineColor(kGray + 2);
389 sideV->SetTransparency(70);
390
391 for (int sgn : {-1, +1}) {
392 const double phi = sgn * halfPhi;
393 const double rMid = kInnerWallRadius_cm + kWallThick_cm + 0.5f * radialLen;
394 const double rad = static_cast<double>(TMath::DegToRad());
395 const double x = rMid * std::cos(phi * rad);
396 const double y = rMid * std::sin(phi * rad);
397 auto* rot = new TGeoRotation();
398 rot->RotateZ(static_cast<Double_t>(phi));
399 auto* tr = new TGeoCombiTrans(static_cast<Double_t>(x),
400 static_cast<Double_t>(y),
401 0.0, rot);
402 petalAsm->AddNode(sideV, (sgn < 0 ? 1 : 2), tr);
403 }
404}
405
406// Build inner layers (L0..L2). L0 may be rectangular (IRIS5) or cylindrical.
407// φ-spans derive from spec gaps/arc; all local placement (no rotation).
408static void addBarrelLayers(TGeoVolume* petalAsm, int nPetals, int petalID, bool rectangularL0, bool fullCylinders)
409{
410 if (!petalAsm) {
411 LOGP(error, "addBarrelLayers: petalAsm is null");
412 return;
413 }
414
415 // Per spec (mm → cm)
416 constexpr double gapL0_cm = 0.163f; // 1.63 mm
417 constexpr double gapL1L2_cm = 0.12f; // 1.2 mm
418 constexpr double arcL0_cm = 0.6247f; // 6.247 mm
419
420 // φ spans
421 const double phiL0_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL0_cm, rL0_cm);
422 const double phiL1_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL1L2_cm, rL1_cm);
423 const double phiL2_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL1L2_cm, rL2_cm);
424
425 const std::string nameL0 =
426 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
428
429 if (!fullCylinders && rectangularL0) {
430 VDRectangularLayer L0(0,
431 nameL0,
432 kX2X0, kL0RectWidth_cm, kLenZ_cm, kLenZ_cm);
433
434 // Correct translation: move to radius + half width along x
435 double x = kL0RectHeight_cm + L0.getChipThickness() / 2.;
436 LOGP(info, "Placing rectangular L0 at r={:.3f} cm (half-width={:.3f} cm)", x, 0.5f * kL0RectWidth_cm);
437 double y = 0.0;
438 double z = 0.0;
439
440 // Correct rotation: rotate 90 degrees around z so long side is horizontal
441 auto* rot = new TGeoRotation();
442 rot->RotateZ(90.0);
443
444 auto* tr = new TGeoCombiTrans(x, y, z, rot);
445 L0.createLayer(petalAsm, tr);
446 registerSensor(makeSensorName(nameL0, 0), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Plane, /*idx*/ 0);
447 } else {
448 VDCylindricalLayer L0(0,
449 nameL0,
450 kX2X0, rL0_cm, phiL0_deg, kLenZ_cm, kLenZ_cm);
451 L0.createLayer(petalAsm, nullptr);
452 registerSensor(makeSensorName(nameL0, 0), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 0);
453 }
454
455 const std::string nameL1 =
456 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
458
459 VDCylindricalLayer L1(1,
460 nameL1,
461 kX2X0, rL1_cm, phiL1_deg, kLenZ_cm, kLenZ_cm);
462 L1.createLayer(petalAsm, nullptr);
463 registerSensor(makeSensorName(nameL1, 1), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 1);
464
465 const std::string nameL2 =
466 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
468
469 VDCylindricalLayer L2(2,
470 nameL2,
471 kX2X0, rL2_cm, phiL2_deg, kLenZ_cm, kLenZ_cm);
472 L2.createLayer(petalAsm, nullptr);
473 registerSensor(makeSensorName(nameL2, 2), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 2);
474}
475
476// Build cold plate (cylindrical) in local coordinates, and add it to the petal assembly.
477static void addColdPlate(TGeoVolume* petalAsm, int nPetals, int petalId, bool fullCylinders = false)
478{
479 if (!petalAsm) {
480 LOGP(error, "addColdPlate: petalAsm is null");
481 return;
482 }
483
484 // Resolve medium: prefer provided medium, otherwise try to fetch from geo manager
485 const TGeoMedium* med = gGeoManager->GetMedium("ALICE3_TRKSERVICES_CERAMIC");
486 if (!med) {
487 LOGP(error, "addColdPlate: can't find the medium.");
488 }
489
490 // Angular span for one petal (deg)
491 constexpr double gapL1L2_cm = 0.12f; // 1.2 mm
492
493 // φ spans
494 const double phiSpanColdplate_deg =
495 fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL1L2_cm, rL2_cm); // L2 gap-defined in normal mode
496 const double halfPhiDeg = 0.5 * phiSpanColdplate_deg;
497 const double startPhi = -halfPhiDeg;
498 const double endPhi = +halfPhiDeg;
499
500 // Build tube segment: inner radius, outer radius = inner + thickness, half-length Z
501 auto* shape = new TGeoTubeSeg(static_cast<Double_t>(kColdplateRadius_cm),
502 static_cast<Double_t>(kColdplateRadius_cm + kColdplateThickness_cm),
503 static_cast<Double_t>(0.5 * kColdplateZ_cm),
504 static_cast<Double_t>(startPhi),
505 static_cast<Double_t>(endPhi));
506
507 TString volName = TString::Format("Petal%d_Coldplate", petalId);
508 auto* coldVol = new TGeoVolume(volName, shape, med);
509 coldVol->SetLineColor(kAzure - 3);
510 coldVol->SetTransparency(10);
511
512 // Place in local petal coordinates (no extra transform); keep object alive by allocating shape/volume on heap.
513 petalAsm->AddNode(coldVol, 1);
514
515 LOGP(info, "Adding cold plate {} r={:.3f} cm t={:.3f} cm Lz={:.3f} cm φ=[{:.3f}, {:.3f}]",
516 volName.Data(), kColdplateRadius_cm, kColdplateThickness_cm, kColdplateZ_cm, startPhi, endPhi);
517}
518
519// Add IRIS service module(s) as aluminum annular cylinders placed outside the petals.
520// The two modules are placed at z = ±(36 + halfLength).
521static void addIRISServiceModules(TGeoVolume* petalAsm, int nPetals)
522{
523 if (!petalAsm) {
524 LOGP(error, "addIRISServiceModules: petalAsm is null");
525 return;
526 }
527
528 auto* matAl = new TGeoMaterial("ALUMINUM", 26.9815, 13, 2.70);
529 const TGeoMedium* med = new TGeoMedium("ALUMINUM", 4, matAl);
530
531 if (!med) {
532 LOGP(error, "addIRISServiceModules: ALUMINUM medium not found.");
533 return;
534 }
535
536 constexpr double radius = 3.2; // cm (inner radius)
537 constexpr double thickness = 0.133; // cm (radial thickness)
538 constexpr double halfLength = 19.5; // cm (half-length along Z)
539 const double rIn = radius;
540 const double rOut = radius + thickness;
541
542 // Petal angular span. If you have an exact half-φ from your walls, use it here.
543 const double halfPhi_deg = 0.5 * (360.0 / double(nPetals));
544
545 // Create shape once and reuse
546 auto* segSh = new TGeoTubeSeg(
547 "IRIS_SERVICE_SEGsh",
548 rIn, rOut,
549 halfLength,
550 -halfPhi_deg, halfPhi_deg);
551
552 // Positive Z module
553 TString namePos = "IRIS_Service_Pos";
554 auto* volPos = new TGeoVolume(namePos, segSh, med);
555 volPos->SetLineColor(kRed + 2);
556 volPos->SetTransparency(50);
557
558 // Negative Z module: reuse same shape object, give different name
559 TString nameNeg = "IRIS_Service_Neg";
560 auto* volNeg = new TGeoVolume(nameNeg, segSh, med);
561 volNeg->SetLineColor(kRed + 2);
562 volNeg->SetTransparency(50);
563
564 // Translations (heap-allocated so ROOT keeps them)
565 const double zpos = 36.0 + halfLength;
566 auto* transPos = new TGeoTranslation(0.0, 0.0, static_cast<Double_t>(zpos));
567 auto* transNeg = new TGeoTranslation(0.0, 0.0, static_cast<Double_t>(-zpos));
568
569 // Add to mother volume
570 petalAsm->AddNode(volPos, 1, transPos);
571 petalAsm->AddNode(volNeg, 2, transNeg);
572
573 LOGP(info, "Added IRIS service modules at z = ±{} cm, r=[{}, {}] cm", zpos, rIn, rOut);
574}
575
576// Only the A-side "inside vacuum" piece participates in the cutout.
577static void addIRISServiceModulesSegmented(TGeoVolume* petalAsm, int nPetals)
578{
579 if (!petalAsm) {
580 LOGP(error, "addIRISServiceModulesSegmented: petalAsm is null");
581 return;
582 }
583
584 // --- Service geometry (same as your previous values)
585 constexpr double rIn = 3.2; // cm
586 constexpr double thickness = 0.133; // cm
587 constexpr double rOut = rIn + thickness;
588 constexpr double halfLen = 19.5; // cm
589 constexpr double z0 = 36.0 + halfLen; // 55.5 cm center of +Z service
590 const double zMinA = z0 - halfLen; // 36.0 cm
591 const double zMaxA = z0 + halfLen; // 75.0 cm
592
593 // --- Vacuum vessel window around z∈[-L/2, +L/2] with wall thickness on +Z side
594 // Keep these in sync with TRKServices::createVacuumCompositeShape()
595 constexpr double vacuumVesselLength = 76.0; // cm
596 constexpr double vacuumVesselThickness = 0.08; // cm (0.8 mm)
597 const double halfVess = 0.5 * vacuumVesselLength; // 38.0 cm
598 const double gapStart = halfVess; // 38.00
599 const double gapEnd = halfVess + vacuumVesselThickness; // 38.08
600
601 // --- Petal φ-span (segment)
602 const double halfPhi = 0.5 * (360.0 / double(nPetals));
603
604 auto* matAl = new TGeoMaterial("ALUMINUM", 26.9815, 13, 2.70);
605 const TGeoMedium* med = new TGeoMedium("ALUMINUM", 4, matAl);
606
607 if (!med) {
608 LOGP(error, "addIRISServiceModules: ALUMINUM medium not found.");
609 return;
610 }
611
612 // =========================
613 // C-side (negative Z) whole
614 // =========================
615 {
616 auto* sh = new TGeoTubeSeg(rIn, rOut, halfLen, -halfPhi, +halfPhi);
617 auto* vN = new TGeoVolume("IRIS_Service_Neg", sh, med);
618 vN->SetLineColor(kRed + 2);
619 vN->SetTransparency(55);
620 petalAsm->AddNode(vN, 1, new TGeoTranslation(0., 0., -(z0)));
621 }
622
623 // =====================================
624 // A-side (positive Z): split with a gap
625 // =====================================
626 // Piece 1 (INSIDE vacuum): z ∈ [zMinA, min(zMaxA, gapStart)] → goes into cutout
627 const double L_inVac = std::max(0.0, std::min(zMaxA, gapStart) - zMinA); // expected ~2.0 cm
628 if (L_inVac > 0) {
629 const double dz = 0.5 * L_inVac;
630 const double zc = zMinA + dz; // center of lower slice, ≈ 37.0 cm
631 auto* sh = new TGeoTubeSeg(rIn, rOut, dz, -halfPhi, halfPhi);
632 sh->SetName("IRIS_SERVICE_POS_INVACsh");
633 auto* vP = new TGeoVolume("IRIS_Service_Pos_InVac", sh, med);
634 vP->SetLineColor(kRed + 2);
635 vP->SetTransparency(55);
636 petalAsm->AddNode(vP, 1, new TGeoTranslation(0., 0., zc));
637 LOGP(info, "IRIS A-side (InVac): z=[{:.3f},{:.3f}] cm, len={:.3f} cm",
638 zc - dz, zc + dz, 2 * dz);
639 } else {
640 LOGP(warning, "IRIS A-side (InVac): no overlap with vacuum (L_inVac<=0)");
641 }
642
643 // Gap (no material): (gapStart, gapEnd) = (38.00, 38.08)
644
645 // Piece 2 (OUT of vacuum): z ∈ [max(zMinA, gapEnd), zMaxA] → NOT in cutout
646 const double L_outVac = std::max(0.0, zMaxA - std::max(zMinA, gapEnd)); // expected ~36.92 cm
647 if (L_outVac > 0) {
648 const double dz = 0.5 * L_outVac;
649 const double zc = std::max(zMinA, gapEnd) + dz; // center of upper slice
650 auto* sh = new TGeoTubeSeg(rIn, rOut, dz, -halfPhi, +halfPhi);
651 sh->SetName("IRIS_SERVICE_POS_OUTVACsh");
652 auto* vP = new TGeoVolume("IRIS_Service_Pos_OutVac", sh, med);
653 vP->SetLineColor(kRed + 1);
654 vP->SetTransparency(70);
655 petalAsm->AddNode(vP, 2, new TGeoTranslation(0., 0., +zc));
656 LOGP(info, "IRIS A-side (OutVac): z=[{:.3f},{:.3f}] cm, len={:.3f} cm",
657 zc - dz, zc + dz, 2 * dz);
658 } else {
659 LOGP(warning, "IRIS A-side (OutVac): no upper piece (L_outVac<=0)");
660 }
661}
662
663// Build disks in local coords: each disk gets only a local Z translation.
664// φ span from gap at rOut.
665static void addDisks(TGeoVolume* petalAsm, int nPetals, int petalID, bool fullCylinders)
666{
667
668 if (!petalAsm) {
669 LOGP(error, "addDisks: petalAsm is null");
670 return;
671 }
672
673 const double phiDisk_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, 2 * kWallThick_cm, diskRin_cm);
674
675 for (int i = 0; i < 6; ++i) {
676 const std::string nameD =
677 std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" +
679
680 VDDiskLayer disk(i,
681 nameD,
682 kX2X0, diskRin_cm, diskRout_cm, phiDisk_deg, diskZ_cm[i]);
683
684 // Local Z placement only
685 auto* tr = new TGeoTranslation(0.0, 0.0, static_cast<Double_t>(disk.getZPosition()));
686 disk.createLayer(petalAsm, tr);
687 registerSensor(makeSensorName(nameD, i), petalID, VDSensorDesc::Region::Disk, VDSensorDesc::Type::Plane, /*idx*/ i);
688 }
689}
690
691// Add Z end-cap walls to "close" the petal/cylinder volume at zMin and zMax.
692// Implemented as thin rings (TGeoTube) with thickness 'capThick_cm' in Z,
693// spanning radii [rIn_cm, rOut_cm].
694static void addPetalEndCaps(TGeoVolume* petalAsm,
695 int petalId,
696 double rIn_cm,
697 double rOut_cm,
698 double zMin_cm,
699 double zMax_cm,
700 double capThick_cm)
701{
702 if (!petalAsm) {
703 LOGP(error, "addPetalEndCaps: petalAsm is null");
704 return;
705 }
706
708 const TGeoMedium* med =
709 matmgr.getTGeoMedium("ALICE3_TRKSERVICES_ALUMINIUM5083");
710
711 if (!med) {
712 LOGP(warning,
713 "addPetalEndCaps: ALICE3_TRKSERVICES_ALUMINIUM5083 not found, caps not created.");
714 return;
715 }
716
717 const double halfT = 0.5 * capThick_cm;
718
719 auto* sh = new TGeoTube(static_cast<Double_t>(rIn_cm),
720 static_cast<Double_t>(rOut_cm),
721 static_cast<Double_t>(halfT));
722
723 TString vname = Form("Petal%d_ZCap", petalId);
724 auto* v = new TGeoVolume(vname, sh, med);
725 v->SetLineColor(kGray + 2);
726 v->SetTransparency(70);
727
728 auto* trMin = new TGeoTranslation(0.0, 0.0,
729 static_cast<Double_t>(zMin_cm + halfT));
730 auto* trMax = new TGeoTranslation(0.0, 0.0,
731 static_cast<Double_t>(zMax_cm - halfT));
732
733 petalAsm->AddNode(v, 1, trMin);
734 petalAsm->AddNode(v, 2, trMax);
735}
736
737// Build one complete petal assembly (walls + L0..L2 + disks) in LOCAL coords.
738static TGeoVolume* buildPetalAssembly(int nPetals,
739 int petalID,
740 bool rectangularL0,
741 bool fullCylinders,
742 bool withSideWalls)
743{
744 auto* petalAsm = new TGeoVolumeAssembly(Form("PETAL_%d", petalID));
745
746 // In the special mode: no side walls, but keep radial walls as FULL cylinders.
747 addPetalWalls(petalAsm, nPetals, kOuterWallRadius_cm,
748 /*withSideWalls=*/withSideWalls,
749 /*fullCylindricalRadialWalls=*/fullCylinders);
750
751 addBarrelLayers(petalAsm, nPetals, petalID, rectangularL0, fullCylinders);
752 addDisks(petalAsm, nPetals, petalID, fullCylinders);
753
754 addColdPlate(petalAsm, nPetals, petalID, /*fullCylinders=*/false);
755 addIRISServiceModulesSegmented(petalAsm, nPetals);
756
757 return petalAsm;
758}
759
760static TGeoVolume* buildFullCylAssembly(int petalID, bool withDisks)
761{
762 // IMPORTANT: keep naming consistent with createIRIS4/5 (PETAL_%d)
763 auto* petalAsm = new TGeoVolumeAssembly(Form("PETAL_%d", petalID));
764
765 // Radial walls only: full 360° cylinders, no side plates
766 addPetalWalls(petalAsm,
767 /*nPetals=*/1,
768 /*outerRadius_cm=*/kOuterWallRadius_cm,
769 /*withSideWalls=*/false,
770 /*fullCylindricalRadialWalls=*/true);
771
772 // --- Z end-cap walls to close the petal in Z ---
773 {
774 const double zMin = -0.5 * kLenZ_cm;
775 const double zMax = +0.5 * kLenZ_cm;
776 const double rIn = kInnerWallRadius_cm;
777 const double rOut = kOuterWallRadius_cm + kWallThick_cm;
778
779 addPetalEndCaps(petalAsm,
780 petalID,
781 rIn,
782 rOut,
783 zMin,
784 zMax,
785 kWallThick_cm);
786 }
787
788 // Full 360° barrel cylinders
789 addBarrelLayers(petalAsm,
790 /*nPetals=*/1,
791 /*petalID=*/petalID,
792 /*rectangularL0=*/false,
793 /*fullCylinders=*/true);
794
795 addColdPlate(petalAsm, 1, petalID, /*fullCylinders=*/true);
796 addIRISServiceModulesSegmented(petalAsm, /*nPetals=*/1);
797
798 // Optionally add full 360° disks
799 if (withDisks) {
800 addDisks(petalAsm,
801 /*nPetals=*/1,
802 /*petalID=*/petalID,
803 /*fullCylinders=*/true);
804 }
805
806 return petalAsm;
807}
808
809// =================== Public entry points ===================
810
811void createIRIS4Geometry(TGeoVolume* motherVolume)
812{
813 if (!motherVolume) {
814 LOGP(error, "createIRIS4Geometry: motherVolume is null");
815 return;
816 }
817
819
820 constexpr int nPetals = 4;
821 for (int p = 0; p < nPetals; ++p) {
822 auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ false,
823 /*fullCylinders=*/false,
824 /*withSideWalls=*/true);
825 // Build the petal-local solids composite once from the FIRST petal
826 if (p == 0) {
827 buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords
828 }
829 const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5);
830 auto* R = new TGeoRotation();
831 R->RotateZ(phiDeg);
832 auto* T = new TGeoCombiTrans(0, 0, 0, R);
833 motherVolume->AddNode(petal, p + 1, T);
834 }
835 buildIrisCutoutFromPetalSolid(nPetals);
836}
837
838void createIRIS5Geometry(TGeoVolume* motherVolume)
839{
840 if (!motherVolume) {
841 LOGP(error, "createIRIS5Geometry: motherVolume is null");
842 return;
843 }
844
846
847 constexpr int nPetals = 4;
848 for (int p = 0; p < nPetals; ++p) {
849 auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ true,
850 /*fullCylinders=*/false,
851 /*withSideWalls=*/true);
852 // Build the petal-local solids composite once from the FIRST petal
853 if (p == 0) {
854 buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords
855 }
856 const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5);
857 auto* R = new TGeoRotation();
858 R->RotateZ(phiDeg);
859 auto* T = new TGeoCombiTrans(0, 0, 0, R);
860 motherVolume->AddNode(petal, p + 1, T);
861 }
862 buildIrisCutoutFromPetalSolid(nPetals);
863}
864
865void createIRIS4aGeometry(TGeoVolume* motherVolume)
866{
867 if (!motherVolume) {
868 LOGP(error, "createIRIS4aGeometry: motherVolume is null");
869 return;
870 }
871
873
874 constexpr int nPetals = 3;
875 for (int p = 0; p < nPetals; ++p) {
876 auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ false,
877 /*fullCylinders=*/false,
878 /*withSideWalls=*/true);
879 // Build the petal-local solids composite once from the FIRST petal
880 if (p == 0) {
881 buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords
882 }
883 const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5);
884 auto* R = new TGeoRotation();
885 R->RotateZ(phiDeg);
886 auto* T = new TGeoCombiTrans(0, 0, 0, R);
887 motherVolume->AddNode(petal, p + 1, T);
888 }
889 buildIrisCutoutFromPetalSolid(nPetals);
890}
891
892void createIRISGeometryFullCyl(TGeoVolume* motherVolume)
893{
894 if (!motherVolume) {
895 LOGP(error, "createIRISGeometryFullCyl: motherVolume is null");
896 return;
897 }
898
900
901 constexpr int nPetals = 1;
902 constexpr int petalID = 0;
903
904 auto* petal = buildFullCylAssembly(petalID, /*withDisks=*/false);
905 motherVolume->AddNode(petal, 1, nullptr);
906
907 buildPetalSolidsComposite(petal);
908 buildIrisCutoutFromPetalSolid(nPetals);
909}
910
911void createIRISGeometryFullCylwithDisks(TGeoVolume* motherVolume)
912{
913 if (!motherVolume) {
914 LOGP(error, "createIRISGeometryFullCylDisks: motherVolume is null");
915 return;
916 }
917
919
920 constexpr int nPetals = 1;
921 constexpr int petalID = 0;
922
923 auto* petal = buildFullCylAssembly(petalID, /*withDisks=*/true);
924 motherVolume->AddNode(petal, 1, nullptr);
925
926 // Same cutout pipeline as createIRIS4/5:
927 buildPetalSolidsComposite(petal);
928 buildIrisCutoutFromPetalSolid(nPetals);
929}
930
931void createSinglePetalDebug(TGeoVolume* motherVolume, int petalID, int nPetals, bool rectangularL0)
932{
933 auto* petal = buildPetalAssembly(nPetals, petalID, rectangularL0, false, true);
934
935 // Optionally rotate the petal for display
936 const double phiDeg = (360.f / static_cast<double>(nPetals)) * (static_cast<double>(petalID) + 0.5f);
937 auto* R = new TGeoCombiTrans(0, 0, 0, new TGeoRotation("", phiDeg, 0, 0));
938 motherVolume->AddNode(petal, 1, R);
939
940 LOGP(info, "Debug: Added Petal{} to {}", petalID, motherVolume->GetName());
941}
942
943} // namespace o2::trk
std::unique_ptr< expressions::Node > node
int32_t i
float float float & zMax
float float & zMin
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
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
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 createIRISGeometryFullCyl(TGeoVolume *motherVolume)
void createIRIS4aGeometry(TGeoVolume *motherVolume)
void createIRIS4Geometry(TGeoVolume *motherVolume)
void createIRISGeometryFullCylwithDisks(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