File indexing completed on 2026-05-07 08:04:39
0001
0002
0003
0004 #include "DD4hep/DetFactoryHelper.h"
0005 #include "DD4hep/Printout.h"
0006 #include "GeometryHelpers.h"
0007 #include <XML/Helper.h>
0008 #include <XML/Utilities.h>
0009 #include <algorithm>
0010 #include <iostream>
0011 #include <math.h>
0012 #include <tuple>
0013 #include <vector>
0014
0015 using namespace dd4hep;
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028 static std::tuple<int, std::pair<int, int>> add_12surface_disk(Detector& desc, Assembly& env,
0029 xml::Collection_t& plm,
0030 SensitiveDetector& sens, int id);
0031
0032
0033 template <class XmlComp> Position get_xml_xyz(XmlComp& comp, dd4hep::xml::Strng_t name) {
0034 Position pos(0., 0., 0.);
0035 if (comp.hasChild(name)) {
0036 auto child = comp.child(name);
0037 pos.SetX(dd4hep::getAttrOrDefault<double>(child, _Unicode(x), 0.));
0038 pos.SetY(dd4hep::getAttrOrDefault<double>(child, _Unicode(y), 0.));
0039 pos.SetZ(dd4hep::getAttrOrDefault<double>(child, _Unicode(z), 0.));
0040 }
0041 return pos;
0042 }
0043
0044 static Volume build_inner_support(Detector& desc, xml_comp_t handle,
0045 xml_coll_t pts_extrudedpolygon) {
0046
0047
0048 Material inner_ring_material = desc.material(handle.materialStr());
0049
0050 double electron_r = handle.attr<double>(_Unicode(electron_r));
0051 double proton_r = handle.attr<double>(_Unicode(proton_r));
0052 double proton_x_offset = handle.attr<double>(_Unicode(proton_x_offset));
0053 double z_length = handle.z_length();
0054
0055 std::vector<double> pt_innerframe_x;
0056 std::vector<double> pt_innerframe_y;
0057 for (xml_coll_t position_i(pts_extrudedpolygon, _U(position)); position_i; ++position_i) {
0058 xml_comp_t position_comp = position_i;
0059 pt_innerframe_x.push_back((position_comp.x()));
0060 pt_innerframe_y.push_back((position_comp.y()));
0061 }
0062
0063 std::vector<double> sec_z = {-z_length / 2., z_length / 2.};
0064 std::vector<double> sec_x = {0., 0.};
0065 std::vector<double> sec_y = {0., 0.};
0066 std::vector<double> zscale = {1., 1.};
0067
0068 ExtrudedPolygon inner_support_envelope(pt_innerframe_x, pt_innerframe_y, sec_z, sec_x, sec_y,
0069 zscale);
0070
0071 double straight_section_tilt = acos((electron_r - proton_r) / proton_x_offset);
0072 double straight_section_length =
0073 proton_x_offset + (proton_r - electron_r) * cos(straight_section_tilt);
0074 Position straight_section_offset{
0075 straight_section_length / 2 + cos(straight_section_tilt) * electron_r,
0076 0.,
0077 0.,
0078 };
0079
0080
0081 double cutout_length = 2 * z_length;
0082
0083 Tube electron_side{0., electron_r, cutout_length / 2};
0084 Tube proton_side{0., proton_r, cutout_length / 2};
0085 Trd1 electron_proton_straight_section{
0086 electron_r * sin(straight_section_tilt),
0087 proton_r * sin(straight_section_tilt),
0088 cutout_length / 2,
0089 straight_section_length / 2,
0090 };
0091 UnionSolid inner_support_hole{
0092 UnionSolid{
0093 electron_side,
0094 proton_side,
0095 Position{proton_x_offset, 0., 0.},
0096 },
0097 electron_proton_straight_section,
0098 Transform3D{straight_section_offset} * RotationZ(90 * deg) * RotationX(90 * deg),
0099 };
0100
0101 SubtractionSolid inner_support{
0102 inner_support_envelope,
0103 inner_support_hole,
0104 };
0105
0106 Volume inner_support_vol{"inner_support_vol", inner_support, inner_ring_material};
0107 inner_support_vol.setVisAttributes(desc.visAttributes(handle.visStr()));
0108 return inner_support_vol;
0109 }
0110
0111
0112 static Ref_t create_detector(Detector& desc, xml::Handle_t handle, SensitiveDetector sens) {
0113 xml::DetElement detElem = handle;
0114 std::string detName = detElem.nameStr();
0115 int detID = detElem.id();
0116 DetElement det(detName, detID);
0117 sens.setType("calorimeter");
0118
0119
0120 dd4hep::xml::setDetectorTypeFlag(detElem, det);
0121
0122
0123 Assembly assembly(detName);
0124
0125
0126 xml::Component plm = detElem.child(_Unicode(placements));
0127
0128 std::map<int, std::pair<int, int>> sectorModuleRowsColumns;
0129 auto addRowColumnNumbers = [§orModuleRowsColumns](int sector, std::pair<int, int> rowcolumn) {
0130 auto it = sectorModuleRowsColumns.find(sector);
0131 if (it != sectorModuleRowsColumns.end()) {
0132 it->second = rowcolumn;
0133 } else {
0134 sectorModuleRowsColumns[sector] = rowcolumn;
0135 }
0136 };
0137
0138 int sector_id = 1;
0139 for (xml::Collection_t disk_12surface(plm, _Unicode(disk_12surface)); disk_12surface;
0140 ++disk_12surface) {
0141 auto [sector, rowcolumn] =
0142 add_12surface_disk(desc, assembly, disk_12surface, sens, sector_id++);
0143 addRowColumnNumbers(sector, rowcolumn);
0144 }
0145
0146 for (auto [sector, rowcolumn] : sectorModuleRowsColumns) {
0147 desc.add(Constant(Form((detName + "_NModules_Sector%d").c_str(), sector),
0148 std::to_string((rowcolumn.first)), std::to_string((rowcolumn.second))));
0149 }
0150
0151
0152 auto pos = get_xml_xyz(detElem, _Unicode(position));
0153 auto rot = get_xml_xyz(detElem, _Unicode(rotation));
0154 Volume motherVol = desc.pickMotherVolume(det);
0155 Transform3D tr =
0156 Translation3D(pos.x(), pos.y(), pos.z()) * RotationZYX(rot.z(), rot.y(), rot.x());
0157 PlacedVolume envPV = motherVol.placeVolume(assembly, tr);
0158 envPV.addPhysVolID("system", detID);
0159 det.setPlacement(envPV);
0160 return det;
0161 }
0162
0163
0164 std::tuple<Volume, Position> build_module(Detector& desc, xml::Collection_t& plm,
0165 SensitiveDetector& sens) {
0166 auto mod = plm.child(_Unicode(module));
0167 auto mx = mod.attr<double>(_Unicode(modulex));
0168 auto my = mod.attr<double>(_Unicode(moduley));
0169 auto mz = mod.attr<double>(_Unicode(modulez));
0170 auto mdz = mod.attr<double>(_Unicode(moduleshift));
0171 Box modshape(mx / 2., my / 2., mz / 2.);
0172 auto modMat = desc.material(mod.attr<std::string>(_Unicode(gmaterial)));
0173 Volume modVol("module_vol", modshape, modMat);
0174 modVol.setVisAttributes(desc.visAttributes(mod.attr<std::string>(_Unicode(vis))));
0175
0176 auto cry = plm.child(_Unicode(crystal));
0177 auto cryx = cry.attr<double>(_Unicode(sizex));
0178 auto cryy = cry.attr<double>(_Unicode(sizey));
0179 auto cryz = cry.attr<double>(_Unicode(sizez));
0180 auto roc = plm.child(_Unicode(readout));
0181 auto PCBx = roc.attr<double>(_Unicode(PCB_sizex));
0182 auto PCBy = roc.attr<double>(_Unicode(PCB_sizex));
0183 auto PCBz = roc.attr<double>(_Unicode(PCB_thickness));
0184 auto sensorx = roc.attr<double>(_Unicode(Sensor_sizex));
0185 auto sensory = roc.attr<double>(_Unicode(Sensor_sizey));
0186 auto sensorz = roc.attr<double>(_Unicode(Sensor_thickness));
0187 auto sensorspace = roc.attr<double>(_Unicode(Sensor_space));
0188 auto sensorNx = roc.attr<int>(_Unicode(Nsensor_X));
0189 auto sensorNy = roc.attr<int>(_Unicode(Nsensor_Y));
0190
0191 Box crystalshape(cryx / 2., cryy / 2., cryz / 2.);
0192 auto crystalMat = desc.material(cry.attr<std::string>(_Unicode(material)));
0193 Volume crystalVol("crystal_vol", crystalshape, crystalMat);
0194 modVol.placeVolume(crystalVol, Position(0., 0., PCBz + sensorz + (cryz - mz) / 2.));
0195 crystalVol.setVisAttributes(desc.visAttributes(cry.attr<std::string>(_Unicode(cryvis))));
0196 crystalVol.setSensitiveDetector(sens);
0197
0198 Box PCBshape(PCBx / 2., PCBy / 2., PCBz / 2.);
0199 auto PCBMat = desc.material(roc.attr<std::string>(_Unicode(material)));
0200 Volume PCBVol("PCB_vol", PCBshape, PCBMat);
0201 modVol.placeVolume(PCBVol, Position(0., 0., (PCBz - mz) / 2.));
0202
0203 Box sensorshape(sensorx / 2., sensory / 2., sensorz / 2.);
0204 auto sensorMat = desc.material(roc.attr<std::string>(_Unicode(material)));
0205 Volume sensorVol("sensor_vol", sensorshape, sensorMat);
0206 auto marginx = (PCBx - sensorNx * sensorx - (sensorNx - 1) * sensorspace) / 2.;
0207 auto marginy = (PCBy - sensorNy * sensory - (sensorNy - 1) * sensorspace) / 2.;
0208 auto x0 = marginx + sensorx / 2. - PCBx / 2.;
0209 auto y0 = marginy + sensory / 2. - PCBy / 2.;
0210 for (int i = 0; i < sensorNx; i++)
0211 for (int j = 0; j < sensorNy; j++)
0212 modVol.placeVolume(sensorVol,
0213 Position(x0 + (sensorx + sensorspace) * i,
0214 y0 + (sensory + sensorspace) * j, PCBz + (sensorz - mz) / 2.));
0215
0216 if (!plm.hasChild(_Unicode(wrapper))) {
0217 printout(DEBUG, "HomogeneousCalorimeter", "without wrapper");
0218 return std::make_tuple(modVol, Position{mx, my, mz});
0219 } else {
0220 auto wrp = plm.child(_Unicode(wrapper));
0221 auto wrapcfthickness = wrp.attr<double>(_Unicode(carbonfiber_thickness));
0222 auto wrapcflength = wrp.attr<double>(_Unicode(carbonfiber_length));
0223 auto wrapVMthickness = wrp.attr<double>(_Unicode(VM2000_thickness));
0224 auto carbonMat = desc.material(wrp.attr<std::string>(_Unicode(material_carbon)));
0225 auto wrpMat = desc.material(wrp.attr<std::string>(_Unicode(material_wrap)));
0226 auto gapMat = desc.material(wrp.attr<std::string>(_Unicode(material_gap)));
0227
0228 if (wrapcfthickness < 1e-12 * mm)
0229 return std::make_tuple(modVol, Position{mx, my, mz});
0230
0231 Box carbonShape(mx / 2., my / 2., wrapcflength / 2.);
0232 Box carbonShape_sub((mx - 2. * wrapcfthickness) / 2., (my - 2. * wrapcfthickness) / 2.,
0233 wrapcflength / 2.);
0234 SubtractionSolid carbon_subtract(carbonShape, carbonShape_sub, Position(0., 0., 0.));
0235
0236 Box gapShape(mx / 2., my / 2., (cryz - 2. * wrapcflength) / 2.);
0237 Box gapShape_sub((mx - 2. * wrapcfthickness) / 2., (my - 2. * wrapcfthickness) / 2.,
0238 (cryz - 2. * wrapcflength) / 2.);
0239 SubtractionSolid gap_subtract(gapShape, gapShape_sub, Position(0., 0., 0.));
0240
0241 Box wrpVM2000((mx - 2. * wrapcfthickness) / 2., (my - 2. * wrapcfthickness) / 2.,
0242 (cryz + mdz) / 2.);
0243 Box wrpVM2000_sub((mx - 2. * wrapcfthickness - 2. * wrapVMthickness) / 2.,
0244 (my - 2. * wrapcfthickness - 2. * wrapVMthickness) / 2., cryz / 2.);
0245 SubtractionSolid wrpVM2000_subtract(wrpVM2000, wrpVM2000_sub, Position(0., 0., -mdz / 2.));
0246
0247 Volume carbonVol("carbon_vol", carbon_subtract, carbonMat);
0248 Volume gapVol("gap_vol", gap_subtract, gapMat);
0249 Volume wrpVol("wrapper_vol", wrpVM2000_subtract, wrpMat);
0250
0251 modVol.placeVolume(carbonVol, Position(0., 0.,
0252 PCBz + sensorz +
0253 (wrapcflength - mz) /
0254 2.));
0255 modVol.placeVolume(carbonVol,
0256 Position(0., 0., PCBz + sensorz + cryz - (wrapcflength + mz) / 2.));
0257 modVol.placeVolume(
0258 gapVol,
0259 Position(0., 0.,
0260 PCBz + sensorz + (cryz - mz) / 2.));
0261 modVol.placeVolume(wrpVol, Position(0., 0., PCBz + sensorz + (cryz + mdz - mz) / 2.));
0262
0263 carbonVol.setVisAttributes(desc.visAttributes(wrp.attr<std::string>(_Unicode(vis_carbon))));
0264 gapVol.setVisAttributes(desc.visAttributes(wrp.attr<std::string>(_Unicode(vis_gap))));
0265 wrpVol.setVisAttributes(desc.visAttributes(wrp.attr<std::string>(_Unicode(vis_wrap))));
0266
0267 printout(DEBUG, "HomogeneousCalorimeter", "with wrapper");
0268
0269 return std::make_tuple(modVol, Position{mx, my, mz});
0270 }
0271 }
0272
0273
0274 static std::tuple<int, std::pair<int, int>> add_12surface_disk(Detector& desc, Assembly& env,
0275 xml::Collection_t& plm,
0276 SensitiveDetector& sens, int sid) {
0277 auto [modVol, modSize] = build_module(desc, plm, sens);
0278 int sector_id = dd4hep::getAttrOrDefault<int>(plm, _Unicode(sector), sid);
0279 double rmax = plm.attr<double>(_Unicode(rmax));
0280 double r12min = plm.attr<double>(_Unicode(r12min));
0281 double r12max = plm.attr<double>(_Unicode(r12max));
0282 double structure_frame_length = plm.attr<double>(_Unicode(outerringlength));
0283 double calo_module_length = plm.attr<double>(_Unicode(modulelength));
0284 double envelope_length = plm.attr<double>(_Unicode(envelope_length));
0285 double Prot = plm.attr<double>(_Unicode(protate));
0286 double Nrot = plm.attr<double>(_Unicode(nrotate));
0287 double Oring_shift = plm.attr<double>(_Unicode(outerringshift));
0288 double Innera = plm.attr<double>(_Unicode(inneradiusa));
0289 double Innerb = plm.attr<double>(_Unicode(inneradiusb));
0290 double phimin = dd4hep::getAttrOrDefault<double>(plm, _Unicode(phimin), 0.);
0291 double phimax = dd4hep::getAttrOrDefault<double>(plm, _Unicode(phimax), 2. * M_PI);
0292 xml_coll_t pts_extrudedpolygon(plm, _Unicode(points_extrudedpolygon));
0293
0294 double half_modx = modSize.x() * 0.5, half_mody = modSize.y() * 0.5;
0295
0296
0297
0298
0299
0300
0301
0302 Material outer_ring_material =
0303 desc.material(getAttrOrDefault<std::string>(plm, _U(material), "StainlessSteelSAE304"));
0304
0305
0306
0307
0308
0309 PolyhedraRegular solid_ring12(12, r12min, r12max, structure_frame_length);
0310 Volume ring12_vol("ring12", solid_ring12, outer_ring_material);
0311 Transform3D tr_global_Oring = RotationZYX(Prot, 0., 0.) * Translation3D(0., 0., Oring_shift);
0312 ring12_vol.setVisAttributes(desc.visAttributes(plm.attr<std::string>(_Unicode(vis_struc))));
0313
0314
0315
0316
0317 bool has_envelope = dd4hep::getAttrOrDefault<bool>(plm, _Unicode(envelope), false);
0318 PolyhedraRegular solid_world(12, 0., r12min, envelope_length);
0319 EllipticalTube solid_sub(Innera, Innerb, envelope_length / 2.);
0320 Transform3D subtract_pos = RotationZYX(Nrot, 0., 0.) * Translation3D(1 * cm, 0., 0.);
0321 SubtractionSolid calo_subtract(solid_world, solid_sub, subtract_pos);
0322 Volume env_vol(std::string(env.name()) + "_envelope", calo_subtract, desc.material("Air"));
0323 Transform3D tr_global = RotationZYX(Prot, 0., 0.) * Translation3D(0., 0., 0.);
0324 env_vol.setVisAttributes(desc.visAttributes(plm.attr<std::string>(_Unicode(vis_steel_gap))));
0325
0326
0327
0328 if (has_envelope) {
0329 env.placeVolume(env_vol, tr_global);
0330 env.placeVolume(ring12_vol, tr_global_Oring);
0331
0332 xml_comp_t collar_comp = plm.child(_Unicode(inner_support));
0333 Volume inner_support_vol = build_inner_support(desc, collar_comp, pts_extrudedpolygon);
0334 env_vol.placeVolume(inner_support_vol,
0335 Transform3D{RotationZ{Nrot}} * Translation3D(collar_comp.x_offset(0.),
0336 collar_comp.y_offset(0.),
0337 collar_comp.z_offset(0.)));
0338 }
0339
0340
0341
0342
0343
0344 xml_comp_t placement = plm.child(_Unicode(placement));
0345 auto points =
0346 epic::geo::fillRectangles({placement.x_offset(0.), placement.y_offset(0.)}, modSize.x(),
0347 modSize.y(), 0., (rmax / std::cos(Prot)), phimin, phimax);
0348
0349 std::pair<double, double> c1(0., 0.);
0350 auto polyVertex = epic::geo::getPolygonVertices(c1, (rmax / std::cos(Prot)), M_PI / 12., 12);
0351 std::vector<epic::geo::Point> out_vertices, in_vertices;
0352 for (auto p : polyVertex) {
0353 epic::geo::Point a = {p.first, p.second};
0354 out_vertices.push_back(a);
0355 }
0356
0357 for (xml_coll_t position_i(pts_extrudedpolygon, _U(position)); position_i; ++position_i) {
0358 xml_comp_t position_comp = position_i;
0359 epic::geo::Point inpt = {position_comp.x(), position_comp.y()};
0360 in_vertices.push_back(inpt);
0361 }
0362
0363 double minX = 0., maxX = 0., minY = 0., maxY = 0.;
0364 for (auto& square : points) {
0365 epic::geo::Point box[4] = {{square.x() + half_modx, square.y() + half_mody},
0366 {square.x() - half_modx, square.y() + half_mody},
0367 {square.x() - half_modx, square.y() - half_mody},
0368 {square.x() + half_modx, square.y() - half_mody}};
0369 if (epic::geo::isBoxTotalInsidePolygon(box, out_vertices)) {
0370 if (square.x() < minX)
0371 minX = square.x();
0372 if (square.y() < minY)
0373 minY = square.y();
0374 if (square.x() > maxX)
0375 maxX = square.x();
0376 if (square.y() > maxY)
0377 maxY = square.y();
0378 }
0379 }
0380
0381 int total_count = 0;
0382 int row = 0, column = 0;
0383 int N_row = std::round((maxY - minY) / modSize.y());
0384 int N_column = std::round((maxX - minX) / modSize.x());
0385 auto rowcolumn = std::make_pair(N_row, N_column);
0386
0387 for (auto& square : points) {
0388 epic::geo::Point box[4] = {{square.x() + half_modx, square.y() + half_mody},
0389 {square.x() - half_modx, square.y() + half_mody},
0390 {square.x() - half_modx, square.y() - half_mody},
0391 {square.x() + half_modx, square.y() - half_mody}};
0392
0393 if (epic::geo::isBoxTotalInsidePolygon(box, out_vertices)) {
0394 if (!epic::geo::isBoxTotalInsidePolygon(box, in_vertices)) {
0395 column = std::round((square.x() - minX) / modSize.x());
0396 row = std::round((maxY - square.y()) / modSize.y());
0397 Transform3D tr_local =
0398 RotationZYX(Nrot, 0.0, 0.0) *
0399 Translation3D(square.x(), square.y(),
0400 std::max((envelope_length - calo_module_length) / 2, 0.));
0401 auto modPV = (has_envelope ? env_vol.placeVolume(modVol, tr_local)
0402 : env.placeVolume(modVol, tr_global * tr_local));
0403 modPV.addPhysVolID("sector", sector_id)
0404 .addPhysVolID("row", row)
0405 .addPhysVolID("column", column);
0406 total_count++;
0407 }
0408 }
0409 }
0410
0411 printout(DEBUG, "HomogeneousCalorimeter_geo", "Number of modules: %d", total_count);
0412 printout(DEBUG, "HomogeneousCalorimeter_geo", "Min X, Y position of module: %.2f, %.2f", minX,
0413 minY);
0414 printout(DEBUG, "HomogeneousCalorimeter_geo", "Max X, Y position of module: %.2f, %.2f", maxX,
0415 maxY);
0416
0417 return {sector_id, rowcolumn};
0418 }
0419
0420
0421 DECLARE_DETELEMENT(epic_HomogeneousCalorimeter, create_detector)