static Ref_t create_detector(Detector& lcdd, xml_h e, SensitiveDetector /*sens*/)
{

  xml_det_t     x_det     = e;
  int           det_id    = x_det.id();
  string        det_name  = x_det.nameStr();
  DetElement    sdet(det_name, det_id);
  bool          reflect   = x_det.reflect();

  Volume envelope = dd4hep::xml::createPlacedEnvelope(lcdd,  e , sdet) ;

  if (lcdd.buildType() == BUILD_ENVELOPE) return sdet ;

  const double phi1 = 0 ;
  const double phi2 = 360.0 * dd4hep::degree;


  for (xml_coll_t c(x_det, Unicode("section")); c; ++c) {

    xml_comp_t xmlSection(c);

    const double zStart       = xmlSection.attr< double > (_Unicode(start));
    const double zEnd         = xmlSection.attr< double > (_Unicode(end));
    const double rInnerStart  = xmlSection.attr< double > (_Unicode(rMin1));
    const double rInnerEnd    = xmlSection.attr< double > (_Unicode(rMin2));
    const double rOuterStart  = xmlSection.attr< double > (_Unicode(rMax1));
    const double rOuterEnd    = xmlSection.attr< double > (_Unicode(rMax2));
    const double thickness    = rOuterStart - rInnerStart;
    Material sectionMat       = lcdd.material(xmlSection.materialStr());
    const std::string volName      = "section_" + xmlSection.nameStr();

    const double zHalf       = fabs(zEnd - zStart) * 0.5; // half z length of the cone
    const double zPosition   = fabs(zEnd + zStart) * 0.5; // middle z position

    // solid for the tube (including vacuum and wall): a solid cone
    ConeSegment tubeSolid(zHalf, rInnerStart, rOuterStart, rInnerEnd, rOuterEnd , phi1, phi2);

    Volume tubeVol(volName + "_pos", tubeSolid, sectionMat);
    tubeVol.setAttributes(lcdd, xmlSection.regionStr(), xmlSection.limitsStr(), xmlSection.visStr());
    envelope.placeVolume(tubeVol, Position(0, 0, zPosition));

    const double dr    = rInnerEnd - rInnerStart ;
    const double theta = atan2(dr , 2.* zHalf) ;

    Vector3D ocon(rInnerStart + 0.5 * (dr + thickness), 0. , 0.);
    Vector3D v(1. , 0. , theta, Vector3D::spherical) ;
    VolCone conSurf1(tubeVol , SurfaceType(SurfaceType::Helper) , 0.5 * thickness  , 0.5 * thickness , v, ocon);
    volSurfaceList(sdet)->push_back(conSurf1);


    if (reflect) {

      Volume tubeVol2(volName + "_neg", tubeSolid, sectionMat);
      tubeVol2.setAttributes(lcdd, xmlSection.regionStr(), xmlSection.limitsStr(), xmlSection.visStr());
      Transform3D Vol2Place(RotationY(-180.0 * dd4hep::degree), Position(0, 0, -1.*zPosition));
      envelope.placeVolume(tubeVol2, Vol2Place);

      VolCone conSurf2(tubeVol2, SurfaceType(SurfaceType::Helper) , 0.5 * thickness  , 0.5 * thickness , v, ocon);
      volSurfaceList(sdet)->push_back(conSurf2);


    }


  }

  return sdet;

}
/** Construction of VTX detector, ported from Mokka driver TubeX01.cc
 *
 *  Mokka History:
 * - first implementation as Tube00: Paulo Mora de Freitas, Sep 2002
 * - modified from Tube00 to Tube01DT: Ties Behnke, 2003-02-11
 * - modified for a crossing angle as TubeX00: Adrian Vogel, 2005-05-18
 * - modified for fancier geometries as TubeX01: Adrian Vogel, 2006-04-20
 *
 *  @author: F.Gaede, DESY, Jan 2014
 *
 */
static Ref_t create_element(Detector& theDetector,
                            xml_h element,
                            SensitiveDetector /*sens*/) {


  //------------------------------------------
  //  See comments starting with '//**' for
  //     hints on porting issues
  //------------------------------------------

  std::cout << "This is the Beampipe:"  << std::endl;

  //Access to the XML File
  xml_det_t xmlBeampipe = element;
  const std::string name = xmlBeampipe.nameStr();


  DetElement tube(  name, xmlBeampipe.id()  ) ;

  // --- create an envelope volume and position it into the world ---------------------
  
  Volume envelope = dd4hep::xml::createPlacedEnvelope( theDetector,  element , tube ) ;
  
  dd4hep::xml::setDetectorTypeFlag( element, tube ) ;

  if( theDetector.buildType() == BUILD_ENVELOPE ) return tube ;
  
  //-----------------------------------------------------------------------------------


  ConicalSupportData* beampipeData = new ConicalSupportData ;

  //######################################################################################################################################################################
  //  code ported from TubeX01::construct() :
  //##################################
  
  //** DD4hep/TGeo seems to need rad (as opposed to the manual)
  const double phi1 = 0 ;
  const double phi2 = 360.0*dd4hep::degree;
  
  //Parameters we have to know about
  dd4hep::xml::Component xmlParameter = xmlBeampipe.child(_Unicode(parameter));
  const double crossingAngle  = xmlParameter.attr< double >(_Unicode(crossingangle))*0.5; //  only half the angle


  double min_radius = 1.e99 ;

  for(xml_coll_t c( xmlBeampipe ,Unicode("section")); c; ++c) {

    xml_comp_t xmlSection( c );
    
    ODH::ECrossType crossType = ODH::getCrossType(xmlSection.attr< std::string >(_Unicode(type)));
    const double zStart       = xmlSection.attr< double > (_Unicode(start));
    const double zEnd         = xmlSection.attr< double > (_Unicode(end));
    const double rInnerStart  = xmlSection.attr< double > (_Unicode(rMin1));
    const double rInnerEnd    = xmlSection.attr< double > (_Unicode(rMin2));
    const double rOuterStart  = xmlSection.attr< double > (_Unicode(rMax1));
    const double rOuterEnd    = xmlSection.attr< double > (_Unicode(rMax2));
    const double thickness    = rOuterStart - rInnerStart;
    Material sectionMat  = theDetector.material(xmlSection.materialStr());
    const std::string volName      = "tube_" + xmlSection.nameStr();

    std::cout << std::setw(8) << zStart      /dd4hep::mm
	      << std::setw(8) << zEnd	     /dd4hep::mm
	      << std::setw(8) << rInnerStart /dd4hep::mm
	      << std::setw(8) << rInnerEnd   /dd4hep::mm
	      << std::setw(8) << rOuterStart /dd4hep::mm
	      << std::setw(8) << rOuterEnd   /dd4hep::mm
	      << std::setw(8) << thickness   /dd4hep::mm
	      << std::setw(8) << crossType
	      << std::setw(35) << volName
	      << std::setw(15) << sectionMat.name()  << std::endl;    

    if( crossType == ODH::kCenter ) { // store only the central sections !
      ConicalSupportData::Section section ;
      section.rInner = rInnerStart ;
      section.rOuter = rOuterStart ;
      section.zPos   = zStart ;
      beampipeData->sections.push_back( section ) ;
    }

    // things which can be calculated immediately
    const double zHalf       = fabs(zEnd - zStart) * 0.5; // half z length of the cone
    const double zPosition   = fabs(zEnd + zStart) * 0.5; // middle z position
    Material coreMaterial    = theDetector.material("beam"); // always the same
    Material wallMaterial    = sectionMat;

    // this could mess up your geometry, so better check it
    if (not checkForSensibleGeometry(crossingAngle, crossType)){

      throw std::runtime_error( " Beampipe_o1_v01_geo.cpp : checkForSensibleGeometry() failed " ) ;
      //      return false;
    }
    const double rotateAngle = getCurrentAngle(crossingAngle, crossType); // for the placement at +z (better make it const now)
    const double mirrorAngle = M_PI - rotateAngle; // for the "mirrored" placement at -z
    // the "mirroring" in fact is done by a rotation of (almost) 180 degrees around the y-axis

    switch (crossType) {
    case ODH::kCenter:
    case ODH::kUpstream:
    case ODH::kDnstream: {
      // a volume on the z-axis, on the upstream branch, or on the downstream branch
      
      // absolute transformations for the placement in the world
      Transform3D transformer(RotationY(rotateAngle), RotateY( Position(0, 0, zPosition), rotateAngle) );
      Transform3D transmirror(RotationY(mirrorAngle), RotateY( Position(0, 0, zPosition), mirrorAngle) );
      
      // solid for the tube (including vacuum and wall): a solid cone
      ConeSegment tubeSolid( zHalf, 0, rOuterStart, 0, rOuterEnd , phi1, phi2);
        
      // tube consists of vacuum
      // place tube twice explicitely so we can attach surfaces to each one
      Volume tubeLog( volName, tubeSolid, coreMaterial ) ;
      Volume tubeLog2( volName, tubeSolid, coreMaterial ) ;

      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume( tubeLog,  transformer );
      envelope.placeVolume( tubeLog2,  transmirror );

      // if inner and outer radii are equal, then omit the tube wall
      if (rInnerStart != rOuterStart || rInnerEnd != rOuterEnd) {
	
	// the wall solid: a tubular cone
	ConeSegment wallSolid( zHalf, rInnerStart, rOuterStart, rInnerEnd, rOuterEnd, phi1, phi2);
        
	// the wall consists of the material given in the XML
	Volume wallLog ( volName + "_wall", wallSolid, wallMaterial);
	Volume wallLog2( volName + "_wall2", wallSolid, wallMaterial);
	
	if( crossType == ODH::kCenter ) { 

	  // add surface for tracking ....
	  const bool isCylinder = ( rInnerStart == rInnerEnd );


	  if (isCylinder) {  // cylinder

	    Vector3D ocyl(  rInnerStart + thickness/2.  , 0. , 0. ) ;

	    VolCylinder cylSurf1( wallLog , SurfaceType( SurfaceType::Helper ) , 0.5*thickness  , 0.5*thickness , ocyl );
	    VolCylinder cylSurf2( wallLog2, SurfaceType( SurfaceType::Helper ) , 0.5*thickness  , 0.5*thickness , ocyl );

	    volSurfaceList( tube )->push_back( cylSurf1 );
	    volSurfaceList( tube )->push_back( cylSurf2 );

	  }else{   // cone 
	    
	    const double dr    = rInnerEnd - rInnerStart ;
	    const double theta = atan2( dr , 2.* zHalf ) ;

	    Vector3D ocon( rInnerStart + 0.5 * ( dr + thickness ), 0. , 0. );

	    Vector3D v( 1. , 0. , theta, Vector3D::spherical ) ;

	    VolCone conSurf1( wallLog , SurfaceType( SurfaceType::Helper ) , 0.5*thickness  , 0.5*thickness , v, ocon );
	    VolCone conSurf2( wallLog2, SurfaceType( SurfaceType::Helper ) , 0.5*thickness  , 0.5*thickness , v, ocon );

	    volSurfaceList( tube )->push_back( conSurf1 );
	    volSurfaceList( tube )->push_back( conSurf2 );

	  }

	  if( rInnerStart < min_radius ) min_radius = rInnerStart ;
	  if( rOuterStart < min_radius ) min_radius = rOuterStart ;
	}

	wallLog.setVisAttributes(theDetector, "TubeVis");
	wallLog2.setVisAttributes(theDetector, "TubeVis");
	tubeLog.setVisAttributes(theDetector, "VacVis");
	tubeLog2.setVisAttributes(theDetector, "VacVis");
	
	// placement as a daughter volume of the tube, will appear in both placements of the tube
	tubeLog.placeVolume( wallLog,  Transform3D() );
	tubeLog2.placeVolume( wallLog2,  Transform3D() );
      }
    }  
      break;
      
    case ODH::kPunchedCenter: {
      // a volume on the z-axis with one or two inner holes
      // (implemented as a cone from which tubes are punched out)
      
      const double rUpstreamPunch = rInnerStart; // just alias names denoting what is meant here
      const double rDnstreamPunch = rInnerEnd; // (the database entries are "abused" in this case)
      
      // relative transformations for the composition of the SubtractionVolumes
      Transform3D upstreamTransformer(RotationY(-crossingAngle), Position(zPosition * tan(-crossingAngle), 0, 0));
      Transform3D dnstreamTransformer(RotationY(+crossingAngle), Position(zPosition * tan(+crossingAngle), 0, 0));
  
      // absolute transformations for the final placement in the world (angles always equal zero and 180 deg)
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY( Position(0, 0, zPosition) , rotateAngle) );
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY( Position(0, 0, zPosition) , mirrorAngle) );
  
      // solid for the tube (including vacuum and wall): a solid cone
      ConeSegment tubeSolid( zHalf, 0, rOuterStart, 0, rOuterEnd, phi1, phi2);
        
      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0( volName + "_0", tubeSolid, coreMaterial );
      Volume tubeLog1( volName + "_1", tubeSolid, coreMaterial );
        
      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume( tubeLog0, placementTransformer );
      envelope.placeVolume( tubeLog1, placementTransmirror );

      // the wall solid and placeholders for possible SubtractionSolids
      ConeSegment wholeSolid(  zHalf, 0, rOuterStart, 0, rOuterEnd, phi1, phi2);
      
      Solid tmpSolid0, tmpSolid1, wallSolid0, wallSolid1;
      
      // the punched subtraction solids can be asymmetric and therefore have to be created twice:
      // one time in the "right" way, another time in the "reverse" way, because the "mirroring"
      // rotation around the y-axis will not only exchange +z and -z, but also +x and -x

      if ( rUpstreamPunch > 1e-6 ) { // do we need a hole on the upstream branch?
       	Tube upstreamPunch( 0, rUpstreamPunch, 5 * zHalf, phi1, phi2); // a bit longer
       	tmpSolid0 = SubtractionSolid( wholeSolid, upstreamPunch, upstreamTransformer);
       	tmpSolid1 = SubtractionSolid( wholeSolid, upstreamPunch, dnstreamTransformer); // [sic]
      } else { // dont't do anything, just pass on the unmodified shape
       	tmpSolid0 = wholeSolid;
       	tmpSolid1 = wholeSolid;
      }
      
      if (rDnstreamPunch > 1e-6 ) { // do we need a hole on the downstream branch?
       	Tube dnstreamPunch( 0, rDnstreamPunch, 5 * zHalf, phi1, phi2); // a bit longer
       	wallSolid0 = SubtractionSolid( tmpSolid0, dnstreamPunch, dnstreamTransformer);
       	wallSolid1 = SubtractionSolid( tmpSolid1, dnstreamPunch, upstreamTransformer); // [sic]
      } else { // dont't do anything, just pass on the unmodified shape
       	wallSolid0 = tmpSolid0;
       	wallSolid1 = tmpSolid1;
      }

      // the wall consists of the material given in the XML
      Volume wallLog0( volName + "_wall_0", wallSolid0, wallMaterial );
      Volume wallLog1( volName + "_wall_1", wallSolid1, wallMaterial );

      wallLog0.setVisAttributes(theDetector, "TubeVis");
      wallLog1.setVisAttributes(theDetector, "TubeVis");
      tubeLog0.setVisAttributes(theDetector, "VacVis");
      tubeLog1.setVisAttributes(theDetector, "VacVis");

      // placement as a daughter volumes of the tube
      tubeLog0.placeVolume( wallLog0, Position() );
      tubeLog1.placeVolume( wallLog1, Position() );
      
      break;
    }

    case ODH::kPunchedUpstream:
    case ODH::kPunchedDnstream: {
      // a volume on the upstream or downstream branch with two inner holes
      // (implemented as a cone from which another tube is punched out)
      
      const double rCenterPunch = (crossType == ODH::kPunchedUpstream) ? (rInnerStart) : (rInnerEnd); // just alias names denoting what is meant here
      const double rOffsetPunch = (crossType == ODH::kPunchedDnstream) ? (rInnerStart) : (rInnerEnd); // (the database entries are "abused" in this case)
      
      // relative transformations for the composition of the SubtractionVolumes
      Transform3D punchTransformer(RotationY(-2 * rotateAngle), Position(zPosition * tan(-2 * rotateAngle), 0, 0));
      Transform3D punchTransmirror(RotationY(+2 * rotateAngle), Position(zPosition * tan(+2 * rotateAngle), 0, 0));
      
      // absolute transformations for the final placement in the world
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY( Position(0, 0, zPosition) , rotateAngle) );
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY( Position(0, 0, zPosition) , mirrorAngle) );
      
      // solid for the tube (including vacuum and wall): a solid cone
      ConeSegment tubeSolid( zHalf, 0, rOuterStart, 0, rOuterEnd, phi1, phi2);
      
      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0( volName + "_0", tubeSolid, coreMaterial );
      Volume tubeLog1( volName + "_1", tubeSolid, coreMaterial );
      
      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume( tubeLog0, placementTransformer );
      envelope.placeVolume( tubeLog1, placementTransmirror );

      // the wall solid and the piece (only a tube, for the moment) which will be punched out
      ConeSegment wholeSolid( zHalf, rCenterPunch , rOuterStart, rCenterPunch, rOuterEnd, phi1, phi2);
      
      Tube punchSolid( 0, rOffsetPunch, 5 * zHalf, phi1, phi2); // a bit longer
      
      // the punched subtraction solids can be asymmetric and therefore have to be created twice:
      // one time in the "right" way, another time in the "reverse" way, because the "mirroring"
      // rotation around the y-axis will not only exchange +z and -z, but also +x and -x
      SubtractionSolid wallSolid0( wholeSolid, punchSolid, punchTransformer);
      SubtractionSolid wallSolid1( wholeSolid, punchSolid, punchTransmirror);

      // the wall consists of the material given in the database
      Volume wallLog0( volName + "_wall_0", wallSolid0, wallMaterial );
      Volume wallLog1( volName + "_wall_1", wallSolid1, wallMaterial );
      
      wallLog0.setVisAttributes(theDetector, "TubeVis");
      wallLog1.setVisAttributes(theDetector, "TubeVis");

      tubeLog0.setVisAttributes(theDetector, "VacVis");
      tubeLog1.setVisAttributes(theDetector, "VacVis");
      
      // placement as a daughter volumes of the tube
      tubeLog0.placeVolume( wallLog0 , Position() );
      tubeLog1.placeVolume( wallLog1 , Position() );
      
      break;
    }
      
    case ODH::kUpstreamClippedFront:
    case ODH::kDnstreamClippedFront:
    case ODH::kUpstreamSlicedFront:
    case ODH::kDnstreamSlicedFront: {
      // a volume on the upstream or donwstream branch, but with the front face parallel to the xy-plane
      // or to a piece tilted in the other direction ("sliced" like a salami with 2 * rotateAngle)
      // (implemented as a slightly longer cone from which the end is clipped off)
      
      // the volume which will be used for clipping: a solid tube
      const double clipSize = rOuterStart; // the right order of magnitude for the clipping volume (alias name)
      Tube clipSolid( 0, 2 * clipSize, clipSize, phi1, phi2); // should be large enough
        
      // relative transformations for the composition of the SubtractionVolumes
      const double clipAngle = (crossType == ODH::kUpstreamClippedFront || crossType == ODH::kDnstreamClippedFront) ? (rotateAngle) : (2 * rotateAngle);
      const double clipShift = (zStart - clipSize) / cos(clipAngle) - (zPosition - clipSize / 2); // question: why is this correct?
      Transform3D clipTransformer(RotationY(-clipAngle), Position(0, 0, clipShift));
      Transform3D clipTransmirror(RotationY(+clipAngle), Position(0, 0, clipShift));
  
      // absolute transformations for the final placement in the world
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY( Position(0, 0, zPosition - clipSize / 2) , rotateAngle) );
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY( Position(0, 0, zPosition - clipSize / 2) , mirrorAngle) );
  
      // solid for the tube (including vacuum and wall): a solid cone

      ConeSegment wholeSolid(  zHalf + clipSize / 2, 0, rOuterStart, 0, rOuterEnd,  phi1, phi2); // a bit longer
  
      // clip away the protruding end
      SubtractionSolid tubeSolid0( wholeSolid, clipSolid, clipTransformer);
      SubtractionSolid tubeSolid1( wholeSolid, clipSolid, clipTransmirror);

      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0( volName + "_0", tubeSolid0, coreMaterial );
      Volume tubeLog1( volName + "_1", tubeSolid1, coreMaterial );
        
      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume( tubeLog0, placementTransformer );
      envelope.placeVolume( tubeLog1, placementTransmirror );
       
      if (rInnerStart != rOuterStart || rInnerEnd != rOuterEnd) {
	// the wall solid: a tubular cone
	ConeSegment wallWholeSolid(  zHalf + clipSize / 2, rInnerStart, rOuterStart, rInnerEnd, rOuterEnd, phi1, phi2); // a bit longer
        
	// clip away the protruding end
	SubtractionSolid wallSolid0( wallWholeSolid, clipSolid, clipTransformer);
	SubtractionSolid wallSolid1( wallWholeSolid, clipSolid, clipTransmirror);
        
	// the wall consists of the material given in the database
	Volume wallLog0( volName + "_wall_0", wallSolid0, wallMaterial );
	Volume wallLog1( volName + "_wall_1", wallSolid1, wallMaterial );

	wallLog0.setVisAttributes(theDetector, "TubeVis");
	wallLog1.setVisAttributes(theDetector, "TubeVis");

	tubeLog0.setVisAttributes(theDetector, "VacVis");
	tubeLog1.setVisAttributes(theDetector, "VacVis");

	// placement as a daughter volumes of the tube
	tubeLog0.placeVolume( wallLog0, Position() );
	tubeLog1.placeVolume( wallLog1, Position() );
      }
    }
      break;

    case ODH::kUpstreamClippedRear:
    case ODH::kDnstreamClippedRear:
    case ODH::kUpstreamSlicedRear:
    case ODH::kDnstreamSlicedRear: {
      // a volume on the upstream or donwstream branch, but with the rear face parallel to the xy-plane
      // or to a piece tilted in the other direction ("sliced" like a salami with 2 * rotateAngle)
      // (implemented as a slightly longer cone from which the end is clipped off)
      
      // the volume which will be used for clipping: a solid tube
      const double clipSize = rOuterEnd; // the right order of magnitude for the clipping volume (alias name)
      Tube clipSolid( 0, 2 * clipSize, clipSize, phi1, phi2); // should be large enough
      
      // relative transformations for the composition of the SubtractionVolumes
      const double clipAngle = (crossType == ODH::kUpstreamClippedRear || crossType == ODH::kDnstreamClippedRear) ? (rotateAngle) : (2 * rotateAngle);
      const double clipShift = (zEnd + clipSize) / cos(clipAngle) - (zPosition + clipSize / 2); // question: why is this correct?
      Transform3D clipTransformer(RotationY(-clipAngle), Position(0, 0, clipShift));
      Transform3D clipTransmirror(RotationY(+clipAngle), Position(0, 0, clipShift));
      
      // absolute transformations for the final placement in the world
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY( Position(0, 0, zPosition + clipSize / 2) , rotateAngle) );
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY( Position(0, 0, zPosition + clipSize / 2) , mirrorAngle) );
      
      // solid for the tube (including vacuum and wall): a solid cone
      ConeSegment wholeSolid( 0, rOuterStart, 0, rOuterEnd, zHalf + clipSize / 2, phi1, phi2); // a bit longer
      
      // clip away the protruding end
      SubtractionSolid tubeSolid0( wholeSolid, clipSolid, clipTransformer);
      SubtractionSolid tubeSolid1( wholeSolid, clipSolid, clipTransmirror);
      
      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0( volName + "_0", tubeSolid0, coreMaterial );
      Volume tubeLog1( volName + "_1", tubeSolid1, coreMaterial );
      
      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume( tubeLog0, placementTransformer );
      envelope.placeVolume( tubeLog1, placementTransmirror );
      
      if (rInnerStart != rOuterStart || rInnerEnd != rOuterEnd) {
	// the wall solid: a tubular cone
	ConeSegment wallWholeSolid( rInnerStart, rOuterStart, rInnerEnd, rOuterEnd, zHalf + clipSize / 2, phi1, phi2); // a bit longer
        
	// clip away the protruding end
	SubtractionSolid wallSolid0( wallWholeSolid, clipSolid, clipTransformer);
	SubtractionSolid wallSolid1( wallWholeSolid, clipSolid, clipTransmirror);
        
	// the wall consists of the material given in the database
	Volume wallLog0( volName + "_wall_0", wallSolid0, wallMaterial );
	Volume wallLog1( volName + "_wall_1", wallSolid1, wallMaterial );

	wallLog0.setVisAttributes(theDetector, "TubeVis");
	wallLog1.setVisAttributes(theDetector, "TubeVis");

	tubeLog0.setVisAttributes(theDetector, "VacVis");
	tubeLog1.setVisAttributes(theDetector, "VacVis");
        
	// placement as a daughter volumes of the tube
	tubeLog0.placeVolume( wallLog0, Transform3D() );
	tubeLog1.placeVolume( wallLog1, Transform3D() );
      }
      break;
    }

    case ODH::kUpstreamClippedBoth:
    case ODH::kDnstreamClippedBoth:
    case ODH::kUpstreamSlicedBoth:
    case ODH::kDnstreamSlicedBoth: {
      // a volume on the upstream or donwstream branch, but with both faces parallel to the xy-plane
      // or to a piece tilted in the other direction ("sliced" like a salami with 2 * rotateAngle)
      // (implemented as a slightly longer cone from which the end is clipped off)
      
      // the volume which will be used for clipping: a solid tube
      const double clipSize = rOuterEnd; // the right order of magnitude for the clipping volume (alias name)
      Tube clipSolid( 0, 2 * clipSize, clipSize, phi1, phi2); // should be large enough
        
      // relative transformations for the composition of the SubtractionVolumes
      const double clipAngle = (crossType == ODH::kUpstreamClippedBoth || crossType == ODH::kDnstreamClippedBoth) ? (rotateAngle) : (2 * rotateAngle);
      const double clipShiftFrnt = (zStart - clipSize) / cos(clipAngle) - zPosition;
      const double clipShiftRear = (zEnd   + clipSize) / cos(clipAngle) - zPosition;
      Transform3D clipTransformerFrnt(RotationY(-clipAngle), Position(0, 0, clipShiftFrnt));
      Transform3D clipTransformerRear(RotationY(-clipAngle), Position(0, 0, clipShiftRear));
      Transform3D clipTransmirrorFrnt(RotationY(+clipAngle), Position(0, 0, clipShiftFrnt));
      Transform3D clipTransmirrorRear(RotationY(+clipAngle), Position(0, 0, clipShiftRear));
  
      // absolute transformations for the final placement in the world
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY( Position(0, 0, zPosition) , rotateAngle) );
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY( Position(0, 0, zPosition) , mirrorAngle) );
      
      // solid for the tube (including vacuum and wall): a solid cone
      ConeSegment wholeSolid( 0, rOuterStart, 0, rOuterEnd, zHalf + clipSize, phi1, phi2); // a bit longer
      
      // clip away the protruding ends
      SubtractionSolid tmpSolid0 ( wholeSolid, clipSolid, clipTransformerFrnt);
      SubtractionSolid tmpSolid1 ( wholeSolid, clipSolid, clipTransmirrorFrnt);
      SubtractionSolid tubeSolid0( tmpSolid0,  clipSolid, clipTransformerRear);
      SubtractionSolid tubeSolid1( tmpSolid1,  clipSolid, clipTransmirrorRear);
        
      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0( volName + "_0", tubeSolid0, coreMaterial );
      Volume tubeLog1( volName + "_1", tubeSolid1, coreMaterial );
        
      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume( tubeLog0, placementTransformer );
      envelope.placeVolume( tubeLog1, placementTransmirror );
       
      if (rInnerStart != rOuterStart || rInnerEnd != rOuterEnd) {
	// the wall solid: a tubular cone
	ConeSegment wallWholeSolid( rInnerStart, rOuterStart, rInnerEnd, rOuterEnd, zHalf + clipSize, phi1, phi2); // a bit longer
        
	// clip away the protruding ends
	SubtractionSolid wallTmpSolid0( wallWholeSolid, clipSolid, clipTransformerFrnt);
	SubtractionSolid wallTmpSolid1( wallWholeSolid, clipSolid, clipTransmirrorFrnt);
	SubtractionSolid wallSolid0   ( wallTmpSolid0,  clipSolid, clipTransformerRear);
	SubtractionSolid wallSolid1   ( wallTmpSolid1,  clipSolid, clipTransmirrorRear);
        
	// the wall consists of the material given in the database
	Volume wallLog0(volName + "_wall_0", wallSolid0, wallMaterial );
	Volume wallLog1(volName + "_wall_1", wallSolid1, wallMaterial );

	wallLog0.setVisAttributes(theDetector, "TubeVis");
	wallLog1.setVisAttributes(theDetector, "TubeVis");

	tubeLog0.setVisAttributes(theDetector, "VacVis");
	tubeLog1.setVisAttributes(theDetector, "VacVis");
        
	// placement as a daughter volumes of the tube
	tubeLog0.placeVolume( wallLog0, Transform3D() );
	tubeLog1.placeVolume( wallLog1, Transform3D() );
      }
      break;
    }
    default: {
      throw std::runtime_error( " Beampipe_o1_v01_geo.cpp : fatal failure !! ??  " ) ;

      //      return false; // fatal failure
    }

    }//end switch
  }//for all xmlSections

  //######################################################################################################################################################################
  

  // add a surface just inside the beampipe for tracking:
  Vector3D oIPCyl( (min_radius-1.e-3)  , 0. , 0.  ) ;
  SimpleCylinder ipCylSurf( envelope , SurfaceType( SurfaceType::Helper ) , 1.e-5  , 1e-5 , oIPCyl ) ;
  // the length does not really matter here as long as it is long enough for all tracks ...
  ipCylSurf->setHalfLength(  100*dd4hep::cm ) ; 
  volSurfaceList( tube )->push_back( ipCylSurf ) ;

  tube.addExtension< ConicalSupportData >( beampipeData ) ;

  //--------------------------------------
  
  tube.setVisAttributes( theDetector, xmlBeampipe.visStr(), envelope );

  // // tube.setPlacement(pv);
  
  return tube;
}
Exemple #3
0
static Ref_t create_detector(Detector& theDetector,
                             xml_h element,
                             SensitiveDetector sens) {
    
    std::cout << __PRETTY_FUNCTION__  << std::endl;
    std::cout << "Here is my LumiCal"  << std::endl;
    std::cout << " and this is the sensitive detector: " << &sens  << std::endl;
    sens.setType("calorimeter");
    //Materials
    Material air = theDetector.air();
    
    //Access to the XML File
    xml_det_t     xmlLumiCal    = element;
    const std::string detName   = xmlLumiCal.nameStr();
    
    DetElement sdet ( detName, xmlLumiCal.id() );
    
    // --- create an envelope volume and position it into the world ---------------------
    
    Volume envelope = dd4hep::xml::createPlacedEnvelope( theDetector, element , sdet ) ;
    DetElement lumiCalDE_1(sdet,"Calorimeter1",1);
    DetElement lumiCalDE_2(sdet,"Calorimeter2",2);

    sdet.setTypeFlag( DetType::CALORIMETER |  DetType::ENDCAP  | DetType::ELECTROMAGNETIC |  DetType::FORWARD ) ;

    if( theDetector.buildType() == BUILD_ENVELOPE ) return sdet ;
    
    //-----------------------------------------------------------------------------------
    
    dd4hep::xml::Dimension dimensions =  xmlLumiCal.dimensions();
    
    //LumiCal Dimensions
    const double lcalInnerR = dimensions.inner_r();
    const double lcalOuterR = dimensions.outer_r();
    const double lcalInnerZ = dimensions.inner_z();
    const double lcalThickness = Layering(xmlLumiCal).totalThickness();
    const double lcalCentreZ = lcalInnerZ+lcalThickness*0.5;
    
    double LumiCal_cell_size      = theDetector.constant<double>("LumiCal_cell_size");
    //========== fill data for reconstruction ============================
    LayeredCalorimeterData* caloData = new LayeredCalorimeterData ;
    caloData->layoutType = LayeredCalorimeterData::EndcapLayout ;
    caloData->inner_symmetry = 0  ; // hardcoded tube
    caloData->outer_symmetry = 0  ; 
    caloData->phi0 = 0 ;
    
    /// extent of the calorimeter in the r-z-plane [ rmin, rmax, zmin, zmax ] in mm.
    caloData->extent[0] = lcalInnerR ;
    caloData->extent[1] = lcalOuterR ;
    caloData->extent[2] = lcalInnerZ ;
    caloData->extent[3] = lcalInnerZ + lcalThickness ;
    
    // counter for the current layer to be placed
    int thisLayerId = 0;
    
    //Parameters we have to know about
    dd4hep::xml::Component xmlParameter = xmlLumiCal.child(_Unicode(parameter));
    const double fullCrossingAngle  = xmlParameter.attr< double >(_Unicode(crossingangle));
    std::cout << " The crossing angle is: " << fullCrossingAngle << " radian"  << std::endl;
    
    
    //Envelope to place the layers in
    Tube envelopeTube (lcalInnerR, lcalOuterR, lcalThickness*0.5 );
    Volume     envelopeVol(detName+"_module",envelopeTube,air);
    envelopeVol.setVisAttributes(theDetector,xmlLumiCal.visStr());
    
    ////////////////////////////////////////////////////////////////////////////////
    // Create all the layers
    ////////////////////////////////////////////////////////////////////////////////
    
    //Loop over all the layer (repeat=NN) sections
    //This is the starting point to place all layers, we need this when we have more than one layer block
    double referencePosition = -lcalThickness*0.5;
    for(dd4hep::xml::Collection_t coll(xmlLumiCal,_U(layer)); coll; ++coll)  {
        dd4hep::xml::Component xmlLayer(coll); //we know this thing is a layer
        
        
        //This just calculates the total size of a single layer
        //Why no convenience function for this?
        double layerThickness = 0;
        for(dd4hep::xml::Collection_t l(xmlLayer,_U(slice)); l; ++l)
            layerThickness += xml_comp_t(l).thickness();
        
        std::cout << "Total Length "    << lcalThickness/dd4hep::cm  << " cm" << std::endl;
        std::cout << "Layer Thickness " << layerThickness/dd4hep::cm << " cm" << std::endl;
        
        //Loop for repeat=NN
        for(int i=0, repeat=xmlLayer.repeat(); i<repeat; ++i)  {
            
            std::string layer_name = detName + dd4hep::xml::_toString(thisLayerId,"_layer%d");
            Tube layer_base(lcalInnerR,lcalOuterR,layerThickness*0.5);
            
            Volume layer_vol(layer_name,layer_base,air);
            
            
            int sliceID=0;
            double inThisLayerPosition = -layerThickness*0.5;
            
            double nRadiationLengths=0.;
            double nInteractionLengths=0.;
            double thickness_sum=0;
            
            LayeredCalorimeterData::Layer caloLayer ;
            
            for(dd4hep::xml::Collection_t collSlice(xmlLayer,_U(slice)); collSlice; ++collSlice)  {
                dd4hep::xml::Component compSlice = collSlice;
                const double      slice_thickness = compSlice.thickness();
                const std::string sliceName = layer_name + dd4hep::xml::_toString(sliceID,"slice%d");
                Material   slice_material  = theDetector.material(compSlice.materialStr());
                
                Tube sliceBase(lcalInnerR,lcalOuterR,slice_thickness/2);
                
                Volume slice_vol (sliceName,sliceBase,slice_material);
                
                nRadiationLengths += slice_thickness/(2.*slice_material.radLength());
                nInteractionLengths += slice_thickness/(2.*slice_material.intLength());
                thickness_sum += slice_thickness/2;
                
                if ( compSlice.isSensitive() )  {

 #if DD4HEP_VERSION_GE( 0, 15 )
                   //Store "inner" quantities
                    caloLayer.inner_nRadiationLengths = nRadiationLengths;
                    caloLayer.inner_nInteractionLengths = nInteractionLengths;
                    caloLayer.inner_thickness = thickness_sum;
                    //Store scintillator thickness
                    caloLayer.sensitive_thickness = slice_thickness;
#endif                    
                    //Reset counters to measure "outside" quantitites
                    nRadiationLengths=0.;
                    nInteractionLengths=0.;
                    thickness_sum = 0.;
                    slice_vol.setSensitiveDetector(sens);
                }
                
                nRadiationLengths += slice_thickness/(2.*slice_material.radLength());
                nInteractionLengths += slice_thickness/(2.*slice_material.intLength());
                thickness_sum += slice_thickness/2;
                
                slice_vol.setAttributes(theDetector,compSlice.regionStr(),compSlice.limitsStr(),compSlice.visStr());
                layer_vol.placeVolume(slice_vol,Position(0,0,inThisLayerPosition+slice_thickness*0.5));
                    
                inThisLayerPosition += slice_thickness;
                ++sliceID;
            }//For all slices in this layer
            
            //-----------------------------------------------------------------------------------------
            
            ///Needs to be innermost face distance
            caloLayer.distance = lcalCentreZ + referencePosition;

#if DD4HEP_VERSION_GE( 0, 15 )
            caloLayer.outer_nRadiationLengths = nRadiationLengths;
            caloLayer.outer_nInteractionLengths = nInteractionLengths;
            caloLayer.outer_thickness = thickness_sum;
#endif            
            
            caloLayer.cellSize0 = LumiCal_cell_size ;
            caloLayer.cellSize1 = LumiCal_cell_size ;
            
            caloData->layers.push_back( caloLayer ) ;
            //-----------------------------------------------------------------------------------------
            
            //Why are we doing this for each layer, this just needs to be done once and then placed multiple times
            //Do we need unique IDs for each piece?
            layer_vol.setVisAttributes(theDetector,xmlLayer.visStr());
            
            Position layer_pos(0,0,referencePosition+0.5*layerThickness);
            referencePosition += layerThickness;
            PlacedVolume pv = envelopeVol.placeVolume(layer_vol,layer_pos);
            pv.addPhysVolID("layer",thisLayerId);
            
            ++thisLayerId;
            
        }//for all layers
        
    }// for all layer collections
    
    const Position bcForwardPos (std::tan(0.5*fullCrossingAngle)*lcalCentreZ,0.0, lcalCentreZ);
    const Position bcBackwardPos(std::tan(0.5*fullCrossingAngle)*lcalCentreZ,0.0,-lcalCentreZ);
    const Rotation3D bcForwardRot ( RotationY(fullCrossingAngle*0.5 ) );
    const Rotation3D bcBackwardRot( RotationZYX ( (M_PI), (M_PI-fullCrossingAngle*0.5), (0.0)));
    
    PlacedVolume pv =
    envelope.placeVolume(envelopeVol, Transform3D( bcForwardRot, bcForwardPos ) );
    pv.addPhysVolID("barrel", 1);
    lumiCalDE_1.setPlacement(pv);

    PlacedVolume pv2 =
    envelope.placeVolume(envelopeVol, Transform3D( bcBackwardRot, bcBackwardPos ) );
    pv2.addPhysVolID("barrel", 2);
    lumiCalDE_2.setPlacement(pv2);
    
    sdet.addExtension< LayeredCalorimeterData >( caloData ) ;
    
    return sdet;
}
static Ref_t create_element(Detector& theDetector, xml_h xmlHandle, SensitiveDetector /*sens*/) {

  //------------------------------------------
  //  See comments starting with '//**' for
  //     hints on porting issues
  //------------------------------------------

  std::cout << "This is the Mask:" << std::endl;

  // Access to the XML File
  xml_det_t xmlMask = xmlHandle;
  const std::string name = xmlMask.nameStr();

  //--------------------------------
  Assembly envelope(name + "_assembly");
  //--------------------------------

  DetElement tube(name, xmlMask.id());

  //  const double phi1 = 0 ;
  //  const double phi2 = 360.0*dd4hep::degree;

  // Parameters we have to know about
  dd4hep::xml::Component xmlParameter = xmlMask.child(_Unicode(parameter));
  const double crossingAngle = xmlParameter.attr<double>(_Unicode(crossingangle)) * 0.5;  //  only half the angle

  for (xml_coll_t c(xmlMask, Unicode("section")); c; ++c) {

    xml_comp_t xmlSection(c);

    ODH::ECrossType crossType = ODH::getCrossType(xmlSection.attr<std::string>(_Unicode(type)));
    const double zStart = xmlSection.attr<double>(_Unicode(start));
    const double zEnd = xmlSection.attr<double>(_Unicode(end));
    const double rInnerStart = xmlSection.attr<double>(_Unicode(rMin1));
    const double rInnerEnd = xmlSection.attr<double>(_Unicode(rMin2));
    const double rOuterStart = xmlSection.attr<double>(_Unicode(rMax1));
    const double rOuterEnd = xmlSection.attr<double>(_Unicode(rMax2));
    const double phi1 = xmlSection.attr<double>(_Unicode(Phi1));
    const double phi2 = xmlSection.attr<double>(_Unicode(Phi2));
    const double thickness = rOuterStart - rInnerStart;
    Material sectionMat = theDetector.material(xmlSection.materialStr());
    const std::string volName = "tube_" + xmlSection.nameStr();

    std::cout << std::setw(8) << zStart << std::setw(8) << zEnd << std::setw(8) << rInnerStart << std::setw(8)
              << rInnerEnd << std::setw(8) << rOuterStart << std::setw(8) << rOuterEnd << std::setw(8) << thickness
              << std::setw(8) << crossType << std::setw(8) << phi1 << std::setw(8) << phi2 << std::setw(15) << volName
              << std::setw(15) << sectionMat.name() << std::endl;

    // things which can be calculated immediately
    const double zHalf = fabs(zEnd - zStart) * 0.5;      // half z length of the cone
    const double zPosition = fabs(zEnd + zStart) * 0.5;  // middle z position
    Material material = sectionMat;

    // this could mess up your geometry, so better check it
    if (not ODH::checkForSensibleGeometry(crossingAngle, crossType)) {
      throw std::runtime_error(" Mask_o1_v01_noRot_geo.cpp : checkForSensibleGeometry() failed ");
    }

    const double rotateAngle =
        getCurrentAngle(crossingAngle, crossType);  // for the placement at +z (better make it const now)
    const double mirrorAngle = M_PI - rotateAngle;  // for the "mirrored" placement at -z
    // the "mirroring" in fact is done by a rotation of (almost) 180 degrees around the y-axis

    switch (crossType) {
    case ODH::kCenter:
    case ODH::kUpstream:
    case ODH::kDnstream: {
      // a volume on the z-axis, on the upstream branch, or on the downstream branch

      // absolute transformations for the placement in the world, rotate over X
      Transform3D transformer(RotationX(rotateAngle), RotateX(Position(0, 0, zPosition), rotateAngle));
      Transform3D transmirror(RotationX(mirrorAngle), RotateX(Position(0, 0, zPosition), mirrorAngle));

      // solid for the tube (including vacuum and wall): a solid cone
      ConeSegment tubeSolid(zHalf, rInnerStart, rOuterStart, rInnerEnd, rOuterEnd, phi1, phi2);

      // tube consists of vacuum
      Volume tubeLog(volName, tubeSolid, material);
      tubeLog.setVisAttributes(theDetector, xmlMask.visStr());

      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume(tubeLog, transformer);
      envelope.placeVolume(tubeLog, transmirror);

    } break;

    case ODH::kPunchedCenter: {
      // a cone with one or two inner holes (two tubes are punched out)

      const double rUpstreamPunch = rInnerStart;  // just alias names denoting what is meant here
      const double rDnstreamPunch = rInnerEnd;    // (the database entries are "abused" in this case)

      // relative transformations for the composition of the SubtractionVolumes
      Transform3D upstreamTransformer(RotationY(-crossingAngle), Position(zPosition * tan(-crossingAngle), 0, 0));
      Transform3D dnstreamTransformer(RotationY(+crossingAngle), Position(zPosition * tan(+crossingAngle), 0, 0));

      // absolute transformations for the final placement in the world (angles always equal zero and 180 deg)
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY(Position(0, 0, zPosition), rotateAngle));
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY(Position(0, 0, zPosition), mirrorAngle));

      // the main solid and the two pieces (only tubes, for the moment) which will be punched out
      ConeSegment wholeSolid(zHalf, 0, rOuterStart, 0, rOuterEnd, phi1, phi2);
      Solid tmpSolid0, tmpSolid1, finalSolid0, finalSolid1;

      // the punched subtraction solids can be asymmetric and therefore have to be created twice:
      // one time in the "right" way, another time in the "reverse" way, because the "mirroring"
      // rotation around the y-axis will not only exchange +z and -z, but also +x and -x

      if (rUpstreamPunch > 1e-6) {                                     // do we need a hole on the upstream branch?
        Tube upstreamPunch(0, rUpstreamPunch, 5 * zHalf, phi1, phi2);  // a bit longer
        tmpSolid0 = SubtractionSolid(wholeSolid, upstreamPunch, upstreamTransformer);
        tmpSolid1 = SubtractionSolid(wholeSolid, upstreamPunch, dnstreamTransformer);  // [sic]
      } else {  // dont't do anything, just pass on the unmodified shape
        tmpSolid0 = wholeSolid;
        tmpSolid1 = wholeSolid;
      }

      if (rDnstreamPunch > 1e-6) {                                     // do we need a hole on the downstream branch?
        Tube dnstreamPunch(0, rDnstreamPunch, 5 * zHalf, phi1, phi2);  // a bit longer
        finalSolid0 = SubtractionSolid(tmpSolid0, dnstreamPunch, dnstreamTransformer);
        finalSolid1 = SubtractionSolid(tmpSolid1, dnstreamPunch, upstreamTransformer);  // [sic]
      } else {  // dont't do anything, just pass on the unmodified shape
        finalSolid0 = tmpSolid0;
        finalSolid1 = tmpSolid1;
      }

      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0(volName + "_0", finalSolid0, material);
      Volume tubeLog1(volName + "_1", finalSolid1, material);
      tubeLog0.setVisAttributes(theDetector, xmlMask.visStr());
      tubeLog1.setVisAttributes(theDetector, xmlMask.visStr());

      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume(tubeLog0, placementTransformer);
      envelope.placeVolume(tubeLog1, placementTransmirror);

      break;
    }

    case ODH::kPunchedUpstream:
    case ODH::kPunchedDnstream: {
      // a volume on the upstream or downstream branch with two inner holes
      // (implemented as a cone from which another tube is punched out)

      const double rCenterPunch = (crossType == ODH::kPunchedUpstream)
          ? (rInnerStart)
          : (rInnerEnd);  // just alias names denoting what is meant here
      const double rOffsetPunch = (crossType == ODH::kPunchedDnstream)
          ? (rInnerStart)
          : (rInnerEnd);  // (the database entries are "abused" in this case)

      // relative transformations for the composition of the SubtractionVolumes
      Transform3D punchTransformer(RotationY(-2 * rotateAngle), Position(zPosition * tan(-2 * rotateAngle), 0, 0));
      Transform3D punchTransmirror(RotationY(+2 * rotateAngle), Position(zPosition * tan(+2 * rotateAngle), 0, 0));

      // absolute transformations for the final placement in the world
      Transform3D placementTransformer(RotationY(rotateAngle), RotateY(Position(0, 0, zPosition), rotateAngle));
      Transform3D placementTransmirror(RotationY(mirrorAngle), RotateY(Position(0, 0, zPosition), mirrorAngle));

      // the main solid and the piece (only a tube, for the moment) which will be punched out
      ConeSegment wholeSolid(zHalf, rCenterPunch, rOuterStart, rCenterPunch, rOuterEnd, phi1, phi2);
      Tube punchSolid(0, rOffsetPunch, 5 * zHalf, phi1, phi2);  // a bit longer

      // the punched subtraction solids can be asymmetric and therefore have to be created twice:
      // one time in the "right" way, another time in the "reverse" way, because the "mirroring"
      // rotation around the y-axis will not only exchange +z and -z, but also +x and -x
      SubtractionSolid finalSolid0(wholeSolid, punchSolid, punchTransformer);
      SubtractionSolid finalSolid1(wholeSolid, punchSolid, punchTransmirror);

      // tube consists of vacuum (will later have two different daughters)
      Volume tubeLog0(volName + "_0", finalSolid0, material);
      Volume tubeLog1(volName + "_1", finalSolid1, material);
      tubeLog0.setVisAttributes(theDetector, xmlMask.visStr());
      tubeLog1.setVisAttributes(theDetector, xmlMask.visStr());

      // placement of the tube in the world, both at +z and -z
      envelope.placeVolume(tubeLog0, placementTransformer);
      envelope.placeVolume(tubeLog1, placementTransmirror);

      break;
    }
    default: { throw std::runtime_error(" Mask_o1_v01_geo.cpp : fatal failure !! ??  "); }

    }  // end switch
  }    // for all xmlSections

  //--------------------------------------
  Volume mother = theDetector.pickMotherVolume(tube);
  PlacedVolume pv(mother.placeVolume(envelope));
  pv.addPhysVolID("system", xmlMask.id());  //.addPhysVolID("side", 0 ) ;

  tube.setVisAttributes(theDetector, xmlMask.visStr(), envelope);

  tube.setPlacement(pv);

  return tube;
}