Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-22 08:06:40

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 - 2026 Ryan Milton, Aiden Wu
0003 // Updated geometry dimensions provided by Eliott Fountain.
0004 /*
0005 ==========================================================================
0006  Implementation of forward insert calorimeter
0007 --------------------------------------------------------------------------
0008  Author: Ryan Milton (UCR)
0009  Insert Redesign: Aiden Wu (ORNL (NGSI))
0010 ==========================================================================
0011 Tile indexing uses side 0 for right (-x) and side 1 for left (+x).
0012 Columns increase from each outer edge inward; rows increase from top to bottom.
0013 Insert-local tile centers in cm:
0014   left:  (x, y) = (27.26 - 4.72*column, 25.96 - 4.72*row) cm
0015   right: (x, y) = (-27.04 + 4.72*column, 25.96 - 4.72*row) cm
0016 ==========================================================================
0017 */
0018 
0019 #include "DD4hep/DetFactoryHelper.h"
0020 #include <XML/Helper.h>
0021 #include <XML/Utilities.h>
0022 #include <array>
0023 #include <cmath>
0024 
0025 using namespace dd4hep;
0026 
0027 static Ref_t createDetector(Detector& desc, xml_h handle, SensitiveDetector sens) {
0028   // Detector identity.
0029   xml_det_t detElem   = handle;
0030   std::string detName = detElem.nameStr();
0031   int detID           = detElem.id();
0032   sens.setType("calorimeter");
0033 
0034   // Detector envelope.
0035   xml_dim_t dim = detElem.dimensions();
0036   double width  = dim.x();
0037   double height = dim.y();
0038   double length = dim.z();
0039 
0040   // World position and container material.
0041   xml_dim_t pos = detElem.position();
0042   Material air  = desc.material("Air");
0043 
0044   // Read component configuration.
0045   const xml::Component& beam_xml         = detElem.child(_Unicode(beampipe_cutout));
0046   const xml::Component& slots_xml        = detElem.child(_Unicode(pcb_slots));
0047   const xml::Component& pcb_xml          = detElem.child(_Unicode(pcb_boards));
0048   const xml::Component& tiles_xml        = detElem.child(_Unicode(tiles));
0049   const xml::Component& casing_xml       = detElem.child(_Unicode(casing));
0050   const xml::Component& cover_xml        = detElem.child(_Unicode(pcb_covers));
0051   const xml::Component& back_cutouts_xml = detElem.child(_Unicode(backplate_cutouts));
0052 
0053   // Read beam-opening geometry.
0054   const double center_x = beam_xml.attr<double>(_Unicode(center_x)) - pos.x();
0055   const double center_y = beam_xml.attr<double>(_Unicode(center_y)) - pos.y();
0056 
0057   // Circle radii.
0058   const double left_radius  = beam_xml.attr<double>(_Unicode(left_radius));
0059   const double right_radius = beam_xml.attr<double>(_Unicode(right_radius));
0060 
0061   // Gap between insert sides.
0062   const double left_right_gap = detElem.attr<double>(_Unicode(left_right_gap));
0063   const double right_split_x  = -left_right_gap / 2. - pos.x();
0064   const double left_split_x   = left_right_gap / 2. - pos.x();
0065 
0066   // Right-side rectangular cutout.
0067   const double rect_width  = beam_xml.attr<double>(_Unicode(rect_width));
0068   const double rect_height = beam_xml.attr<double>(_Unicode(rect_height));
0069   const double rect_x      = right_split_x - center_x - rect_width / 2.;
0070 
0071   // Horizontal PCB slots.
0072   const double h_pcb_slot_width  = slots_xml.attr<double>(_Unicode(horizontal_width));
0073   const double h_pcb_slot_height = slots_xml.attr<double>(_Unicode(horizontal_height));
0074   const double h_pcb_slot_y      = (rect_height + h_pcb_slot_height) / 2.;
0075 
0076   // Vertical PCB slots.
0077   const double v_pcb_slot_width        = slots_xml.attr<double>(_Unicode(vertical_width));
0078   const double right_v_pcb_slot_height = slots_xml.attr<double>(_Unicode(right_vertical_height));
0079   const double left_v_pcb_slot_height  = slots_xml.attr<double>(_Unicode(left_vertical_height));
0080   const double v_pcb_slot_offset_y     = slots_xml.attr<double>(_Unicode(vertical_offset_y));
0081   const double right_v_pcb_slot_y =
0082       height / 2. - v_pcb_slot_offset_y - right_v_pcb_slot_height / 2.;
0083   const double left_v_pcb_slot_y = height / 2. - v_pcb_slot_offset_y - left_v_pcb_slot_height / 2.;
0084 
0085   // Vertical PCB slot x positions.
0086   const double right_v_pcb_slot_x = right_split_x - v_pcb_slot_width / 2. - center_x;
0087   const double left_v_pcb_slot_x  = left_split_x + v_pcb_slot_width / 2. - center_x;
0088 
0089   // PCB length, thickness, and slot clearance.
0090   const double pcb_length         = pcb_xml.attr<double>(_Unicode(length));
0091   const double pcb_thickness      = pcb_xml.attr<double>(_Unicode(thickness));
0092   const double pcb_edge_clearance = pcb_xml.attr<double>(_Unicode(edge_clearance));
0093 
0094   // Steel casing dimensions.
0095   const double casing_length            = casing_xml.attr<double>(_Unicode(length));
0096   const double casing_thickness         = casing_xml.attr<double>(_Unicode(thickness));
0097   const double casing_left_radius       = casing_xml.attr<double>(_Unicode(left_radius));
0098   const double cover_thickness          = cover_xml.attr<double>(_Unicode(thickness));
0099   const double right_back_cutout_margin = back_cutouts_xml.attr<double>(_Unicode(right_margin));
0100   const double left_back_cutout_margin  = back_cutouts_xml.attr<double>(_Unicode(left_margin));
0101   const double back_cutout_vertical_margin =
0102       back_cutouts_xml.attr<double>(_Unicode(vertical_margin));
0103   const double right_back_cutout_width =
0104       width / 2. - left_right_gap / 2. - pos.x() - 2. * right_back_cutout_margin;
0105   const double left_back_cutout_width =
0106       width / 2. - left_right_gap / 2. + pos.x() - 2. * left_back_cutout_margin;
0107   const double back_cutout_height =
0108       height / 2. - center_y - rect_height / 2. - 2. * back_cutout_vertical_margin;
0109 
0110   // Derive PCB placement and clearances.
0111   // Shorten each PCB along its slot by the edge clearance at both ends.
0112   const double h_pcb_width        = h_pcb_slot_width - 2. * pcb_edge_clearance;
0113   const double right_v_pcb_height = right_v_pcb_slot_height - 2. * pcb_edge_clearance;
0114   const double left_v_pcb_height  = left_v_pcb_slot_height - 2. * pcb_edge_clearance;
0115 
0116   // Align the PCB front with the casing's inner face.
0117   const double pcb_z = -(length - pcb_length) / 2. + casing_thickness;
0118 
0119   // Define the physical tile layout.
0120   // Tile size, gap, and pitch.
0121   const double tile_size  = tiles_xml.attr<double>(_Unicode(size));
0122   const double tile_gap   = tiles_xml.attr<double>(_Unicode(gap));
0123   const double tile_pitch = tile_size + tile_gap;
0124 
0125   // Make the gap from the right tile grid to the central PCB slot four times its outer-edge gap.
0126   const double right_tile_outer_margin =
0127       (width / 2. + center_x + right_v_pcb_slot_x - v_pcb_slot_width / 2. -
0128        (8. * tile_size + 7. * tile_gap)) /
0129       5.;
0130   const double left_x0  = width / 2. - tile_gap - tile_size / 2.;
0131   const double right_x0 = -width / 2. + right_tile_outer_margin + tile_size / 2.;
0132   const double top_y0   = 5.5 * tile_pitch;
0133 
0134   // Number of tiles in each row, ordered from top to bottom.
0135   const std::array<int, 12> left_tiles_per_row  = {4, 4, 4, 3, 3, 2, 2, 3, 3, 4, 4, 4};
0136   const std::array<int, 12> right_tiles_per_row = {8, 8, 4, 3, 2, 2, 2, 2, 3, 4, 8, 8};
0137 
0138   // Place left-side tiles from the +x outer edge inward and from top to bottom.
0139   auto left_tile_position = [left_x0, top_y0, tile_pitch](int row, int column) {
0140     double x = left_x0 - column * tile_pitch;
0141     double y = top_y0 - row * tile_pitch;
0142     return Position(x, y, 0.);
0143   };
0144 
0145   // Place right-side tiles from the -x outer edge inward and from top to bottom.
0146   auto right_tile_position = [right_x0, top_y0, tile_pitch](int row, int column) {
0147     double x = right_x0 + column * tile_pitch;
0148     double y = top_y0 - row * tile_pitch;
0149     return Position(x, y, 0.);
0150   };
0151 
0152   // Build the beampipe hole and PCB slots for the layer container and its passive slices.
0153   auto make_cutout = [&](double thickness, int side) {
0154     Solid cutout = Tube(0., left_radius, thickness / 2.);
0155 
0156     // Add the right-side rectangle and PCB slots to its circular cutout.
0157     if (side == 0) {
0158       Box rect_cutout(rect_width / 2., rect_height / 2., thickness / 2.);
0159       Tube right_circle(0., right_radius, thickness / 2.);
0160       cutout = UnionSolid(right_circle, rect_cutout, Position(rect_x, 0., 0.));
0161 
0162       Box h_pcb_slot(h_pcb_slot_width / 2., h_pcb_slot_height / 2., thickness / 2.);
0163       cutout = UnionSolid(cutout, h_pcb_slot, Position(rect_x, h_pcb_slot_y, 0.));
0164       cutout = UnionSolid(cutout, h_pcb_slot, Position(rect_x, -h_pcb_slot_y, 0.));
0165 
0166       Box v_pcb_slot(v_pcb_slot_width / 2., right_v_pcb_slot_height / 2., thickness / 2.);
0167       cutout = UnionSolid(cutout, v_pcb_slot, Position(right_v_pcb_slot_x, right_v_pcb_slot_y, 0.));
0168       cutout =
0169           UnionSolid(cutout, v_pcb_slot, Position(right_v_pcb_slot_x, -right_v_pcb_slot_y, 0.));
0170     }
0171 
0172     // Add the left-side PCB slots to its circular cutout.
0173     else {
0174       Box v_pcb_slot(v_pcb_slot_width / 2., left_v_pcb_slot_height / 2., thickness / 2.);
0175       cutout = UnionSolid(cutout, v_pcb_slot, Position(left_v_pcb_slot_x, left_v_pcb_slot_y, 0.));
0176       cutout = UnionSolid(cutout, v_pcb_slot, Position(left_v_pcb_slot_x, -left_v_pcb_slot_y, 0.));
0177     }
0178 
0179     return cutout;
0180   };
0181 
0182   // Insert assembly.
0183   Assembly assembly(detName);
0184   // FIXME Workaround for https://github.com/eic/epic/issues/411
0185   assembly.setVisAttributes(desc.visAttributes("InvisibleWithDaughters"));
0186   PlacedVolume pv;
0187 
0188   // Build the right and left halves.
0189   for (int side = 0; side < 2; side++) { // 0 = right (-x), 1 = left (+x)
0190     std::string side_name = side == 1 ? "L" : "R";
0191 
0192     double z_distance_traversed = 0.;
0193     int layer_num               = 1;
0194 
0195     // Build every longitudinal layer section.
0196     for (xml_coll_t c(detElem, _U(layer)); c; c++) {
0197       xml_comp_t x_layer     = c;
0198       int repeat             = x_layer.repeat();
0199       double layer_thickness = x_layer.thickness();
0200 
0201       // Build the layers in this section.
0202       for (int i = 0; i < repeat; i++) {
0203         std::string layer_name = detName + _toString(layer_num, "_layer%d") + "_" + side_name;
0204         Box layer(width / 2., height / 2., layer_thickness / 2.);
0205 
0206         // Build the layer container with the side-specific cutout.
0207         Solid layer_cutout = make_cutout(layer_thickness, side);
0208         Solid layer_with_cutout =
0209             SubtractionSolid(layer, layer_cutout, Position(center_x, center_y, 0.));
0210 
0211         // Extend the left-side Air container around two tiles that cross the circular cutout.
0212         if (side == 1) {
0213           Box tile_pocket(tile_size / 2., tile_size / 2., layer_thickness / 2.);
0214           layer_with_cutout = UnionSolid(layer_with_cutout, tile_pocket, left_tile_position(4, 2));
0215           layer_with_cutout = UnionSolid(layer_with_cutout, tile_pocket, left_tile_position(7, 2));
0216         }
0217 
0218         // Side boundary.
0219         Box side_cut(width / 2., height, layer_thickness);
0220         Position side_cut_position(
0221             side == 0 ? right_split_x + width / 2. : left_split_x - width / 2., 0., 0.);
0222         SubtractionSolid layer_side_with_cutout(layer_with_cutout, side_cut, side_cut_position);
0223         Volume layer_vol(layer_name, layer_side_with_cutout, air);
0224 
0225         int slice_num  = 1;
0226         double slice_z = -layer_thickness / 2.;
0227 
0228         // Build each material slice.
0229         for (xml_coll_t l(x_layer, _U(slice)); l; l++) {
0230           xml_comp_t x_slice     = l;
0231           double slice_thickness = x_slice.thickness();
0232           std::string slice_name = layer_name + _toString(slice_num, "slice%d");
0233           Material slice_mat     = desc.material(x_slice.materialStr());
0234           slice_z += slice_thickness / 2.;
0235 
0236           // Build the sensitive tiles.
0237           if (x_slice.isSensitive()) {
0238             Assembly tile_assembly(slice_name + "_tiles");
0239             Box tile(tile_size / 2., tile_size / 2., slice_thickness / 2.);
0240             Volume tile_vol(slice_name + "_tile", tile, slice_mat);
0241 
0242             // Tile attributes.
0243             tile_vol.setSensitiveDetector(sens);
0244             tile_vol.setAttributes(desc, x_slice.regionStr(), x_slice.limitsStr(),
0245                                    x_slice.visStr());
0246 
0247             // Place every tile row.
0248             for (std::size_t row = 0; row < left_tiles_per_row.size(); row++) {
0249 
0250               int tiles_in_row = side == 0 ? right_tiles_per_row[row] : left_tiles_per_row[row];
0251 
0252               // Place every tile in this row.
0253               for (int column = 0; column < tiles_in_row; column++) {
0254 
0255                 // Place the tile on the selected side.
0256                 Position tile_position =
0257                     side == 0 ? right_tile_position(row, column) : left_tile_position(row, column);
0258                 PlacedVolume tile_pv = tile_assembly.placeVolume(tile_vol, tile_position);
0259 
0260                 // Tile IDs.
0261                 tile_pv.addPhysVolID("x", column).addPhysVolID("y", row);
0262               }
0263             }
0264 
0265             // Tile assembly.
0266             pv = layer_vol.placeVolume(
0267                 tile_assembly, Transform3D(RotationZYX(0, 0, 0), Position(0., 0., slice_z)));
0268             pv.addPhysVolID("slice", slice_num);
0269             pv.addPhysVolID("side", side);
0270             slice_z += slice_thickness / 2.;
0271             z_distance_traversed += slice_thickness;
0272             slice_num++;
0273             continue;
0274           }
0275 
0276           // Passive slice.
0277           Box slice(width / 2., height / 2., slice_thickness / 2.);
0278           Solid slice_cutout = make_cutout(slice_thickness, side);
0279           SubtractionSolid slice_with_cutout(slice, slice_cutout, Position(center_x, center_y, 0.));
0280           Box side_cut_slice(width / 2., height, layer_thickness);
0281           Position side_cut_position_slice(
0282               side == 0 ? right_split_x + width / 2. : left_split_x - width / 2., 0, 0.);
0283           SubtractionSolid slice_side_with_cutout(slice_with_cutout, side_cut_slice,
0284                                                   side_cut_position_slice);
0285           Volume slice_vol(slice_name, slice_side_with_cutout, slice_mat);
0286 
0287           // Slice attributes.
0288           slice_vol.setAttributes(desc, x_slice.regionStr(), x_slice.limitsStr(), x_slice.visStr());
0289 
0290           // Slice placement.
0291           pv = layer_vol.placeVolume(slice_vol,
0292                                      Transform3D(RotationZYX(0, 0, 0), Position(0., 0., slice_z)));
0293           pv.addPhysVolID("slice", slice_num);
0294           pv.addPhysVolID("side", side);
0295           slice_z += slice_thickness / 2.;
0296           z_distance_traversed += slice_thickness;
0297           slice_num++;
0298         }
0299 
0300         // Layer attributes.
0301         layer_vol.setAttributes(desc, x_layer.regionStr(), x_layer.limitsStr(), x_layer.visStr());
0302 
0303         // Layer placement.
0304         pv = assembly.placeVolume(
0305             layer_vol, Transform3D(RotationZYX(0, 0, 0),
0306                                    Position(0., 0.,
0307                                             -length / 2. + casing_thickness +
0308                                                 (z_distance_traversed - layer_thickness) +
0309                                                 layer_thickness / 2.)));
0310 
0311         pv.addPhysVolID("layer", layer_num);
0312         pv.addPhysVolID("side", side);
0313         layer_num++;
0314       }
0315     }
0316   }
0317 
0318   // Build one casing box and divide it into right and left halves.
0319   Material casing_material   = desc.material(casing_xml.materialStr());
0320   const double casing_width  = width + 2. * casing_thickness;
0321   const double casing_height = height + 2. * casing_thickness;
0322   const double casing_z      = -(length - casing_length) / 2.;
0323 
0324   Box casing_outer(casing_width / 2., casing_height / 2., casing_length / 2.);
0325   Box casing_inner(width / 2., height / 2., casing_length / 2. - casing_thickness);
0326   SubtractionSolid casing_box(casing_outer, casing_inner);
0327   const double back_cutout_y = (height / 2. + center_y + rect_height / 2.) / 2.;
0328   const double back_cutout_z = casing_length / 2. - casing_thickness / 2.;
0329 
0330   // Loop through both sides.
0331   for (int side = 0; side < 2; side++) {
0332     Box side_cut(casing_width / 2., casing_height, casing_length);
0333     Position side_cut_position(
0334         side == 0 ? right_split_x + casing_width / 2. : left_split_x - casing_width / 2., 0., 0.);
0335     Solid casing_side = SubtractionSolid(casing_box, side_cut, side_cut_position);
0336 
0337     // Cut the side's beampipe opening through the casing.
0338     Solid casing_cutout = Tube(0., casing_left_radius, casing_length / 2. + casing_thickness);
0339     if (side == 0) {
0340       const double right_rect_width = right_split_x - center_x;
0341       Tube right_cap(0., rect_height / 2., casing_length / 2. + casing_thickness, M_PI / 2.,
0342                      3. * M_PI / 2.);
0343       Box right_rect(right_rect_width / 2., rect_height / 2.,
0344                      casing_length / 2. + casing_thickness);
0345       casing_cutout = UnionSolid(right_cap, right_rect, Position(right_rect_width / 2., 0., 0.));
0346     }
0347 
0348     Solid casing_with_cutout =
0349         SubtractionSolid(casing_side, casing_cutout, Position(center_x, center_y, 0.));
0350 
0351     // Cut mirrored openings through each rear plate.
0352     const double cutout_width = side == 0 ? right_back_cutout_width : left_back_cutout_width;
0353     const double cutout_x     = side == 0 ? (-casing_width / 2. + right_split_x) / 2.
0354                                           : (left_split_x + casing_width / 2.) / 2.;
0355     Box back_cutout(cutout_width / 2., back_cutout_height / 2., casing_thickness);
0356     casing_with_cutout      = SubtractionSolid(casing_with_cutout, back_cutout,
0357                                                Position(cutout_x, back_cutout_y, back_cutout_z));
0358     casing_with_cutout      = SubtractionSolid(casing_with_cutout, back_cutout,
0359                                                Position(cutout_x, -back_cutout_y, back_cutout_z));
0360     std::string casing_name = detName + (side == 0 ? "_RightCasing" : "_LeftCasing");
0361     Volume casing_vol(casing_name, casing_with_cutout, casing_material);
0362     casing_vol.setVisAttributes(desc.visAttributes(casing_xml.visStr()));
0363     assembly.placeVolume(casing_vol, Position(0., 0., casing_z));
0364   }
0365 
0366   // Keep shared cover geometry inputs available for the remaining PCB covers.
0367   const double left_split_from_center = left_split_x - center_x;
0368   Material cover_material             = desc.material(cover_xml.materialStr());
0369 
0370   // FIXME: The left conical PCB cover is omitted because it intersects tiles
0371   // (column,row)=(2,4) and (2,7); its material contribution should be negligible.
0372   /*
0373   const double cover_phi = std::acos(left_split_from_center / casing_left_radius);
0374   ConeSegment conical_cover(pcb_length / 2., casing_left_radius - cover_thickness,
0375                             casing_left_radius,
0376                             casing_left_radius - cover_thickness, casing_left_radius,
0377                             -cover_phi, cover_phi);
0378   Volume cover_vol(detName + "_LeftPCBConicalCover", conical_cover, cover_material);
0379   cover_vol.setVisAttributes(desc.visAttributes(cover_xml.visStr()));
0380   assembly.placeVolume(cover_vol, Position(center_x, center_y, pcb_z));
0381   */
0382 
0383   // Place Horizontal PCBs.
0384   Material pcb_material = desc.material("Fr4");
0385   Box h_pcb(h_pcb_width / 2., pcb_thickness / 2., pcb_length / 2.);
0386   Volume h_pcb_vol(detName + "_HorizontalPCB", h_pcb, pcb_material);
0387   h_pcb_vol.setVisAttributes(desc.visAttributes("AnlDarkGreen"));
0388   assembly.placeVolume(h_pcb_vol, Position(center_x + rect_x, center_y + h_pcb_slot_y, pcb_z));
0389   assembly.placeVolume(h_pcb_vol, Position(center_x + rect_x, center_y - h_pcb_slot_y, pcb_z));
0390 
0391   // Line the top and bottom edges of the rectangular beampipe opening.
0392   Box h_pcb_cover(rect_width / 2., cover_thickness / 2., pcb_length / 2.);
0393   Volume h_pcb_cover_vol(detName + "_HorizontalPCBCover", h_pcb_cover, cover_material);
0394   h_pcb_cover_vol.setVisAttributes(desc.visAttributes(cover_xml.visStr()));
0395   assembly.placeVolume(
0396       h_pcb_cover_vol,
0397       Position(center_x + rect_x, center_y + rect_height / 2. - cover_thickness / 2., pcb_z));
0398   assembly.placeVolume(
0399       h_pcb_cover_vol,
0400       Position(center_x + rect_x, center_y - rect_height / 2. + cover_thickness / 2., pcb_z));
0401 
0402   // Place vertical PCBs for the right side.
0403   Box right_v_pcb(pcb_thickness / 2., right_v_pcb_height / 2., pcb_length / 2.);
0404   Volume right_v_pcb_vol(detName + "_RightVerticalPCB", right_v_pcb, pcb_material);
0405   right_v_pcb_vol.setVisAttributes(desc.visAttributes("AnlDarkGreen"));
0406   assembly.placeVolume(right_v_pcb_vol, Position(center_x + right_v_pcb_slot_x,
0407                                                  center_y + right_v_pcb_slot_y, pcb_z));
0408   assembly.placeVolume(right_v_pcb_vol, Position(center_x + right_v_pcb_slot_x,
0409                                                  center_y - right_v_pcb_slot_y, pcb_z));
0410 
0411   // Then place the covers for the right vertical PCBs.
0412   const double right_v_pcb_cover_inner_y = rect_height / 2. - cover_thickness;
0413   const double right_v_pcb_cover_outer_y = height / 2. + casing_thickness;
0414   const double right_v_pcb_cover_height  = right_v_pcb_cover_outer_y - right_v_pcb_cover_inner_y;
0415   const double right_v_pcb_cover_y = (right_v_pcb_cover_inner_y + right_v_pcb_cover_outer_y) / 2.;
0416   Box right_v_pcb_cover(cover_thickness / 2., right_v_pcb_cover_height / 2., pcb_length / 2.);
0417   Volume right_v_pcb_cover_vol(detName + "_RightVerticalPCBCover", right_v_pcb_cover,
0418                                cover_material);
0419   right_v_pcb_cover_vol.setVisAttributes(desc.visAttributes(cover_xml.visStr()));
0420   assembly.placeVolume(
0421       right_v_pcb_cover_vol,
0422       Position(center_x + right_v_pcb_slot_x + v_pcb_slot_width / 2. + cover_thickness / 2.,
0423                center_y + right_v_pcb_cover_y, pcb_z));
0424   assembly.placeVolume(
0425       right_v_pcb_cover_vol,
0426       Position(center_x + right_v_pcb_slot_x + v_pcb_slot_width / 2. + cover_thickness / 2.,
0427                center_y - right_v_pcb_cover_y, pcb_z));
0428 
0429   // Place vertical PCBs for the left side.
0430   Box left_v_pcb(pcb_thickness / 2., left_v_pcb_height / 2., pcb_length / 2.);
0431   Volume left_v_pcb_vol(detName + "_LeftVerticalPCB", left_v_pcb, pcb_material);
0432   left_v_pcb_vol.setVisAttributes(desc.visAttributes("AnlDarkGreen"));
0433   assembly.placeVolume(left_v_pcb_vol,
0434                        Position(center_x + left_v_pcb_slot_x, center_y + left_v_pcb_slot_y, pcb_z));
0435   assembly.placeVolume(left_v_pcb_vol,
0436                        Position(center_x + left_v_pcb_slot_x, center_y - left_v_pcb_slot_y, pcb_z));
0437 
0438   // Then place the covers for the left vertical PCBs.
0439   const double left_v_pcb_cover_inner_y = std::sqrt(
0440       casing_left_radius * casing_left_radius - left_split_from_center * left_split_from_center);
0441   const double left_v_pcb_cover_outer_y = height / 2. + casing_thickness;
0442   const double left_v_pcb_cover_height  = left_v_pcb_cover_outer_y - left_v_pcb_cover_inner_y;
0443   const double left_v_pcb_cover_y = (left_v_pcb_cover_inner_y + left_v_pcb_cover_outer_y) / 2.;
0444   Box left_v_pcb_cover(cover_thickness / 2., left_v_pcb_cover_height / 2., pcb_length / 2.);
0445   Volume left_v_pcb_cover_vol(detName + "_LeftVerticalPCBCover", left_v_pcb_cover, cover_material);
0446   left_v_pcb_cover_vol.setVisAttributes(desc.visAttributes(cover_xml.visStr()));
0447   assembly.placeVolume(
0448       left_v_pcb_cover_vol,
0449       Position(center_x + left_v_pcb_slot_x - v_pcb_slot_width / 2. - cover_thickness / 2.,
0450                center_y + left_v_pcb_cover_y, pcb_z));
0451   assembly.placeVolume(
0452       left_v_pcb_cover_vol,
0453       Position(center_x + left_v_pcb_slot_x - v_pcb_slot_width / 2. - cover_thickness / 2.,
0454                center_y - left_v_pcb_cover_y, pcb_z));
0455 
0456   DetElement det(detName, detID);
0457   Volume motherVol = desc.pickMotherVolume(det);
0458 
0459   // Detector flags.
0460   dd4hep::xml::setDetectorTypeFlag(detElem, det);
0461 
0462   // World placement.
0463   auto tr          = Transform3D(Position(pos.x(), pos.y(), pos.z() + length / 2.));
0464   PlacedVolume phv = motherVol.placeVolume(assembly, tr);
0465   phv.addPhysVolID("system", detID);
0466   det.setPlacement(phv);
0467 
0468   return det;
0469 }
0470 DECLARE_DETELEMENT(epic_InsertCalorimeter, createDetector)