diff --git a/Core/include/Acts/Surfaces/detail/AnnulusBoundsHelper.hpp b/Core/include/Acts/Surfaces/detail/AnnulusBoundsHelper.hpp new file mode 100644 index 00000000000..5cd081bbdee --- /dev/null +++ b/Core/include/Acts/Surfaces/detail/AnnulusBoundsHelper.hpp @@ -0,0 +1,40 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" + +#include +#include +#include + +namespace Acts { + +class AnnulusBounds; + +namespace detail::AnnulusBoundsHelper { + +/// @brief The factory function to create an annulus bounds +/// +/// @param transform the transform of final surface object +/// @param rMin minimal radius in disc frame +/// @param rMax maximal radius in disc frame +/// @param vertices the vertices of the cutout trapezoid +/// +/// @note - for the time being, the vertices follow ROOT::TGeo convention +/// i.e. [0 - 3] vertices defince clockwise (sic!) the plane at -z +/// and [4 - 7] vertices define clockwise (sic!) the plane at +z +/// +/// @return AnnulusBounds +std::tuple, Transform3> create( + const Transform3& transform, ActsScalar rMin, ActsScalar rMax, + std::vector vertices); + +} // namespace detail::AnnulusBoundsHelper +} // namespace Acts diff --git a/Core/src/Surfaces/CMakeLists.txt b/Core/src/Surfaces/CMakeLists.txt index dc517f15a08..ed12621bc1e 100644 --- a/Core/src/Surfaces/CMakeLists.txt +++ b/Core/src/Surfaces/CMakeLists.txt @@ -23,8 +23,9 @@ target_sources( SurfaceArray.cpp SurfaceError.cpp TrapezoidBounds.cpp - detail/AlignmentHelper.cpp VerticesHelper.cpp RegularSurface.cpp CurvilinearSurface.cpp + detail/AlignmentHelper.cpp + detail/AnnulusBoundsHelper.cpp ) diff --git a/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp b/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp new file mode 100644 index 00000000000..da3fe516c16 --- /dev/null +++ b/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp @@ -0,0 +1,65 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Surfaces/detail/AnnulusBoundsHelper.hpp" + +#include "Acts/Surfaces/AnnulusBounds.hpp" +#include "Acts/Utilities/VectorHelpers.hpp" + +#include + +std::tuple, Acts::Transform3> +Acts::detail::AnnulusBoundsHelper::create(const Transform3& transform, + ActsScalar rMin, ActsScalar rMax, + std::vector vertices) { + using Line2D = Eigen::Hyperplane; + + // Construct the bound lines + std::vector> boundLines; + for (std::size_t i = 0; i < vertices.size(); ++i) { + Vector2 a = vertices.at(i); + Vector2 b = vertices.at((i + 1) % vertices.size()); + Vector2 ab = b - a; + double phi = VectorHelpers::phi(ab); + + if (std::abs(phi) > 3 * M_PI / 4. || std::abs(phi) < M_PI / 4.) { + if (a.norm() < b.norm()) { + boundLines.push_back(std::make_pair(a, b)); + } else { + boundLines.push_back(std::make_pair(b, a)); + } + } + } + + if (boundLines.size() != 2) { + throw std::logic_error( + "Input DiscPoly bounds type does not have sensible edges."); + } + + Line2D lA = Line2D::Through(boundLines[0].first, boundLines[0].second); + Line2D lB = Line2D::Through(boundLines[1].first, boundLines[1].second); + Vector2 ix = lA.intersection(lB); + + const Eigen::Translation3d originTranslation(ix.x(), ix.y(), 0.); + const Vector2 originShift = -ix; + + // Update transform by prepending the origin shift translation + Transform3 boundsTransform = transform * originTranslation; + // Transform phi line point to new origin and get phi + double phi1 = VectorHelpers::phi(boundLines[0].second - boundLines[0].first); + double phi2 = VectorHelpers::phi(boundLines[1].second - boundLines[1].first); + double phiMax = std::max(phi1, phi2); + double phiMin = std::min(phi1, phi2); + double phiShift = 0.; + + // Create the bounds + auto annulusBounds = std::make_shared( + rMin, rMax, phiMin, phiMax, originShift, phiShift); + + return std::make_tuple(annulusBounds, boundsTransform); +} diff --git a/Examples/Python/src/GeoModel.cpp b/Examples/Python/src/GeoModel.cpp index 896f9dd3aed..73c13a4c9dc 100644 --- a/Examples/Python/src/GeoModel.cpp +++ b/Examples/Python/src/GeoModel.cpp @@ -6,9 +6,19 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Must be on top to avoid some conflict between forward declare and typedef +// clang-format off +#include "GeoModelRead/ReadGeoModel.h" +// clang-format on + +#include "Acts/Plugins/GeoModel/GeoModelConverters.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorSurfaceFactory.hpp" #include "Acts/Plugins/GeoModel/GeoModelReader.hpp" #include "Acts/Plugins/GeoModel/GeoModelTree.hpp" +#include "Acts/Plugins/GeoModel/IGeoShapeConverter.hpp" #include "Acts/Plugins/Python/Utilities.hpp" +#include "Acts/Surfaces/Surface.hpp" #include @@ -29,5 +39,97 @@ void addGeoModel(Context& ctx) { py::class_(gm, "GeoModelTree").def(py::init<>()); gm.def("readFromDb", &Acts::GeoModelReader::readFromDb); + + py::class_>( + gm, "GeoModelDetectorElement"); + + // Shape converters + { + py::class_>(gm, + "IGeoShapeConverter"); + + py::class_>(gm, "GeoBoxConverter") + .def(py::init<>()) + .def("toSensitiveSurface", &Acts::GeoBoxConverter::toSensitiveSurface) + .def("toPassiveSurface", &Acts::GeoBoxConverter::toPassiveSurface); + + py::class_>(gm, "GeoTrdConverter") + .def(py::init<>()) + .def("toSensitiveSurface", &Acts::GeoTrdConverter::toSensitiveSurface) + .def("toPassiveSurface", &Acts::GeoTrdConverter::toPassiveSurface); + + py::class_>(gm, "GeoTubeConverter") + .def(py::init<>()) + .def("toSensitiveSurface", &Acts::GeoTubeConverter::toSensitiveSurface) + .def("toPassiveSurface", &Acts::GeoTubeConverter::toPassiveSurface); + + py::class_>( + gm, "GeoUnionDoubleTrdConverter") + .def(py::init<>()) + .def("toSensitiveSurface", + &Acts::GeoUnionDoubleTrdConverter::toSensitiveSurface) + .def("toPassiveSurface", + &Acts::GeoUnionDoubleTrdConverter::toPassiveSurface); + + py::class_>( + gm, "GeoIntersectionAnnulusConverter") + .def(py::init<>()) + .def("toSensitiveSurface", + &Acts::GeoIntersectionAnnulusConverter::toSensitiveSurface) + .def("toPassiveSurface", + &Acts::GeoIntersectionAnnulusConverter::toPassiveSurface); + + py::class_>(gm, + "GeoShiftConverter") + .def(py::init<>()) + .def("toSensitiveSurface", &Acts::GeoShiftConverter::toSensitiveSurface) + .def("toPassiveSurface", &Acts::GeoShiftConverter::toPassiveSurface); + } + + // Surface factory + { + auto f = + py::class_>( + gm, "GeoModelDetectorSurfaceFactory") + .def(py::init( + [](const Acts::GeoModelDetectorSurfaceFactory::Config& cfg, + Acts::Logging::Level level) { + return std::make_shared( + cfg, Acts::getDefaultLogger( + "GeoModelDetectorSurfaceFactory", level)); + })) + .def("construct", &Acts::GeoModelDetectorSurfaceFactory::construct); + + py::class_(f, "Config") + .def(py::init<>()) + .def_readwrite( + "shapeConverters", + &Acts::GeoModelDetectorSurfaceFactory::Config::shapeConverters) + .def_readwrite("nameList", + &Acts::GeoModelDetectorSurfaceFactory::Config::nameList) + .def_readwrite( + "materialList", + &Acts::GeoModelDetectorSurfaceFactory::Config::materialList); + + py::class_(f, "Cache") + .def(py::init<>()) + .def_readwrite( + "sensitiveSurfaces", + &Acts::GeoModelDetectorSurfaceFactory::Cache::sensitiveSurfaces); + + py::class_(f, "Options") + .def(py::init<>()) + .def_readwrite("queries", + &Acts::GeoModelDetectorSurfaceFactory::Options::queries); + } } } // namespace Acts::Python diff --git a/Examples/Python/src/Obj.cpp b/Examples/Python/src/Obj.cpp index 6f2239ca7b0..378b548707c 100644 --- a/Examples/Python/src/Obj.cpp +++ b/Examples/Python/src/Obj.cpp @@ -34,13 +34,16 @@ void addObj(Context& ctx) { /// @param surfaces is the collection of surfaces /// @param viewContext is the geometry context /// @param viewRgb is the color of the surfaces + /// @param viewSegements is the number of segments to approximate a full circle /// @param fileName is the path to the output file /// mex.def("writeSurfacesObj", [](const std::vector>& surfaces, const GeometryContext& viewContext, - const std::array& viewRgb, const std::string& fileName) { + const std::array& viewRgb, unsigned int viewSegements, + const std::string& fileName) { Acts::ViewConfig sConfig = Acts::ViewConfig{viewRgb}; + sConfig.nSegments = viewSegements; Acts::GeometryView3D view3D; Acts::ObjVisualization3D obj; diff --git a/Examples/Scripts/Python/geomodel.py b/Examples/Scripts/Python/geomodel.py new file mode 100644 index 00000000000..75720f88c71 --- /dev/null +++ b/Examples/Scripts/Python/geomodel.py @@ -0,0 +1,69 @@ +import acts +import argparse +from acts import logging, GeometryContext +from acts import geomodel as gm +from acts import examples + + +def main(): + p = argparse.ArgumentParser() + + p.add_argument("-i", "--input", type=str, default="", help="Input SQL file") + + p.add_argument( + "-q", + "--queries", + type=str, + nargs="+", + default="GeoModelXML", + help="List of Queries for Published full phys volumes", + ) + + p.add_argument( + "--output-obj", + help="Write the surfaces to OBJ files", + action="store_true", + default=False, + ) + + args = p.parse_args() + + gContext = acts.GeometryContext() + + # Read the geometry model from the database + gmTree = acts.geomodel.readFromDb(args.input) + + gmFactoryConfig = gm.GeoModelDetectorSurfaceFactory.Config() + gmFactoryConfig.shapeConverters = [ + gm.GeoBoxConverter(), + gm.GeoTrdConverter(), + gm.GeoIntersectionAnnulusConverter(), + gm.GeoShiftConverter(), + gm.GeoUnionDoubleTrdConverter(), + ] + gmFactoryConfig.nameList = [] + gmFactoryConfig.materialList = [ + "std::Silicon", + ] + + gmFactory = gm.GeoModelDetectorSurfaceFactory(gmFactoryConfig, logging.VERBOSE) + # The options + gmFactoryOptions = gm.GeoModelDetectorSurfaceFactory.Options() + gmFactoryOptions.queries = args.queries + # The Cache & construct call + gmFactoryCache = gm.GeoModelDetectorSurfaceFactory.Cache() + gmFactory.construct(gmFactoryCache, gContext, gmTree, gmFactoryOptions) + + # Output the surface to an OBJ file + segments = 720 + if args.output_obj: + ssurfaces = [ss[1] for ss in gmFactoryCache.sensitiveSurfaces] + acts.examples.writeSurfacesObj( + ssurfaces, gContext, [75, 220, 100], segments, "geomodel.obj" + ) + + return + + +if "__main__" == __name__: + main() diff --git a/Plugins/GeoModel/CMakeLists.txt b/Plugins/GeoModel/CMakeLists.txt index a7fc08498f7..65f50bcef83 100644 --- a/Plugins/GeoModel/CMakeLists.txt +++ b/Plugins/GeoModel/CMakeLists.txt @@ -2,7 +2,16 @@ include(FetchContent) add_library( ActsPluginGeoModel SHARED - src/GeoModelReader.cpp) + src/GeoModelConversionError.cpp + src/GeoModelReader.cpp + src/GeoModelDetectorElement.cpp + src/GeoModelDetectorSurfaceFactory.cpp + src/detail/GeoBoxConverter.cpp + src/detail/GeoTrdConverter.cpp + src/detail/GeoTubeConverter.cpp + src/detail//GeoShiftConverter.cpp + src/detail//GeoIntersectionAnnulusConverter.cpp + src/detail/GeoUnionDoubleTrdConverter.cpp) target_include_directories( ActsPluginGeoModel PUBLIC diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelConversionError.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelConversionError.hpp new file mode 100644 index 00000000000..a5e93f0d53b --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelConversionError.hpp @@ -0,0 +1,34 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include +#include + +class GeoFullPhysVol; + +namespace Acts { + +enum class GeoModelConversionError { + // ensure all values are non-zero + WrongShapeForConverter = 1, + InvalidShapeParameters, + UnkownShape, + MissingLogicalVolume +}; + +std::error_code make_error_code(Acts::GeoModelConversionError e); + +} // namespace Acts + +namespace std { +// register with STL +template <> +struct is_error_code_enum : std::true_type {}; +} // namespace std diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelConverters.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelConverters.hpp new file mode 100644 index 00000000000..b9dc0a5d90f --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelConverters.hpp @@ -0,0 +1,71 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Plugins/GeoModel/IGeoShapeConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GenericGeoShapeConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoBoxConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoIntersectionAnnulusConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoShiftConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoTrdConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoTubeConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoUnionDoubleTrdConverter.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +#include +#include +#include + +namespace Acts { + +/// @brief The GeoBox converter +/// +/// This is a dedicated converter for GeoBox shapes +using GeoBoxConverter = + detail::GenericGeoShapeConverter; + +/// @brief A dedicated converter for GeoInterseciton that describe annulus bounds +/// +/// This is very much tailored to the AnnulusBounds class +using GeoIntersectionAnnulusConverter = + detail::GenericGeoShapeConverter; + +/// @brief The GeoShift + Trd/Box/Tube converter +/// +/// This is a dedicated converter for GeoBox shapes +using GeoShiftConverter = + detail::GenericGeoShapeConverter; + +/// @brief The GeoTrd converter +/// +/// This is a dedicated converter for GeoTrd shapes +using GeoTrdConverter = + detail::GenericGeoShapeConverter; + +/// @brief The GeoTube converter +/// +/// This is a dedicated converter for GeoTube shapes +using GeoTubeConverter = + detail::GenericGeoShapeConverter; + +/// @brief The GeoTube converter +/// +/// This is a dedicated converter for GeoTube shapes +using GeoUnionDoubleTrdConverter = + detail::GenericGeoShapeConverter; + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp new file mode 100644 index 00000000000..6e702dc6a8b --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp @@ -0,0 +1,112 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Geometry/DetectorElementBase.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Surfaces/Surface.hpp" + +#include + +class GeoFullPhysVol; + +namespace Acts { + +class CylinderBounds; +class LineBounds; +class PlanarBounds; + +/// @class GeoModelDetectorElement +/// +/// Detector element representative for GeoModel based +/// sensitive elements. +class GeoModelDetectorElement : public DetectorElementBase { + public: + /// Broadcast the context type + using ContextType = GeometryContext; + + // Deleted default constructor + GeoModelDetectorElement() = delete; + + /// @brief Factory to create a planar detector element with connected surface + /// + /// @tparam SurfaceType the surface type + /// @tparam BoundsType the bounds type + /// + /// @param geoPhysVol representing the physical volume + /// @param bounds the bounds class + /// @param sfTransform the surface transform + /// @param thickness the thickness of the detector element + /// + /// @return a shared pointer to an instance of the detector element + template + static std::shared_ptr createDetectorElement( + const GeoFullPhysVol& geoPhysVol, + const std::shared_ptr bounds, const Transform3& sfTransform, + ActsScalar thickness) { + // First create the detector element with a nullptr + auto detElement = std::make_shared( + geoPhysVol, nullptr, sfTransform, thickness); + auto surface = Surface::makeShared(bounds, *detElement.get()); + detElement->attachSurface(surface); + return detElement; + } + + /// Constructor with arguments + /// + /// @param geoPhysVol representing the physical volume + /// @param surface the representing surface + /// @param sfTransform the surface transform + /// @param thickness the thickness of the detector element + GeoModelDetectorElement(const GeoFullPhysVol& geoPhysVol, + std::shared_ptr surface, + const Transform3& sfTransform, ActsScalar thickness); + + /// Return local to global transform associated with this detector element + /// + /// @param gctx The current geometry context object, e.g. alignment + const Transform3& transform(const GeometryContext& gctx) const override; + + /// Return surface associated with this detector element + const Surface& surface() const override; + + /// Non-const access to surface associated with this detector element + Surface& surface() override; + + /// Return the thickness of this detector element + ActsScalar thickness() const override; + + /// @return to the Geant4 physical volume + const GeoFullPhysVol& physicalVolume() const; + + private: + /// Attach a surface + /// + /// @param surface The surface to attach + void attachSurface(std::shared_ptr surface) { + m_surface = std::move(surface); + } + + /// The GeoModel full physical volume + const GeoFullPhysVol* m_geoPhysVol{nullptr}; + /// The surface + std::shared_ptr m_surface; + /// The global transformation before the volume + Transform3 m_surfaceTransform; + /// Thickness of this detector element + ActsScalar m_thickness{0.}; +}; + +/// Collect the sensitive surface & detector element +using GeoModelSensitiveSurface = + std::tuple, + std::shared_ptr>; + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelDetectorSurfaceFactory.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelDetectorSurfaceFactory.hpp new file mode 100644 index 00000000000..13631293e9a --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelDetectorSurfaceFactory.hpp @@ -0,0 +1,93 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Plugins/GeoModel/IGeoShapeConverter.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include +#include + +class GeoShape; +class GeoFullPhysVol; + +namespace Acts { + +struct GeoModelTree; +class Surface; + +/// A factory to convert GeoModel volume into sensitive +/// or passive surfaces which are filled into a Cache object, +/// also the create GeoModelDetectorElements which are also +/// returned. +class GeoModelDetectorSurfaceFactory { + public: + /// Collect the passive surfaces, bool whether it should be + /// added as an "always try, i.e. assignToAll=true" surface + using GeoModelPassiveSurface = std::tuple, bool>; + + // Configuration struct for the Detector surface factory + struct Config { + /// The shape converters to be use + std::vector> shapeConverters = {}; + /// List for names to match + std::vector nameList; + /// List for materials to match + std::vector materialList; + }; + + /// Nested cache that records the conversion status + struct Cache { + /// The created detector elements and their surfaces + std::vector sensitiveSurfaces; + /// The created passive representation surfaces + std::vector passiveSurfaces; + }; + + /// The options to steer the conversion + struct Options { + std::vector queries = {}; + }; + + /// The GeoModel detector element factory + /// + /// @param cfg the configuration struct + /// @param mlogger a screen output logger + GeoModelDetectorSurfaceFactory( + const Config& cfg, + std::unique_ptr mlogger = getDefaultLogger( + "GeoModelDetectorSurfaceFactory", Acts::Logging::INFO)); + + /// Construction method of the detector elements + /// + /// @param cache [in,out] into which the Elements are filled + /// @param gctx the geometry context + /// @param geoModelTree the gjeo model tree + /// @param options to steer the conversion + /// + /// @note this method will call the recursive construction + void construct(Cache& cache, const GeometryContext& gctx, + const GeoModelTree& geoModelTree, const Options& options); + + private: + /// The configuration struct + Config m_cfg; + + /// Logging instance + std::unique_ptr m_logger; + + /// Private access to the logger + const Logger& logger() const { return *m_logger; } +}; + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelReader.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelReader.hpp index f5eacf4079d..8342edd5f30 100644 --- a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelReader.hpp +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/GeoModelReader.hpp @@ -10,6 +10,7 @@ #include "Acts/Plugins/GeoModel/GeoModelTree.hpp" +#include #include namespace Acts::GeoModelReader { diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/IGeoShapeConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/IGeoShapeConverter.hpp new file mode 100644 index 00000000000..fb9f0f94052 --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/IGeoShapeConverter.hpp @@ -0,0 +1,49 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +class GeoFullPhysVol; + +namespace Acts { + +class Surface; + +/// @class IGeoShapeConverter +/// +/// Interface for the conversion of GeoShapes to Acts surfaces or volumes +class IGeoShapeConverter { + public: + /// Virtual destructor + virtual ~IGeoShapeConverter() = default; + + /// @brief Convert a GeoShape to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// + /// @return The detector element and surface + virtual Result toSensitiveSurface( + const GeoFullPhysVol& geoFPV) const = 0; + + /// @brief Convert a GeoShape to a detector element and passive surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// + /// @return The representing surface + virtual Result> toPassiveSurface( + const GeoFullPhysVol& geoFPV) const = 0; +}; + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GenericGeoShapeConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GenericGeoShapeConverter.hpp new file mode 100644 index 00000000000..617df570534 --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GenericGeoShapeConverter.hpp @@ -0,0 +1,67 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Plugins/GeoModel/IGeoShapeConverter.hpp" + +#include +#include + +namespace Acts::detail { + +template +struct GenericGeoShapeConverter : public IGeoShapeConverter { + Acts::Result toSensitiveSurface( + const GeoFullPhysVol& geoFPV) const override { + // Retrieve logical volume and absolute transform + const GeoLogVol* logVol = geoFPV.getLogVol(); + const Transform3& transform = geoFPV.getAbsoluteTransform(nullptr); + if (logVol != nullptr) { + const GeoShape* geoShape = logVol->getShape(); + auto concreteShape = dynamic_cast(geoShape); + if (concreteShape != nullptr) { + return Converter{}(geoFPV, *concreteShape, transform, true); + } + return Result::failure( + GeoModelConversionError::WrongShapeForConverter); + } + return Result::failure( + GeoModelConversionError::MissingLogicalVolume); + } + + Acts::Result> toPassiveSurface( + const GeoFullPhysVol& geoFPV) const override { + // Retrieve logical volume and absolute transform + const GeoLogVol* logVol = geoFPV.getLogVol(); + const Transform3& transform = geoFPV.getAbsoluteTransform(nullptr); + if (logVol != nullptr) { + const GeoShape* geoShape = logVol->getShape(); + + auto concreteShape = dynamic_cast(geoShape); + if (concreteShape != nullptr) { + // Conversion function call with sensitive = false + auto res = Converter{}(geoFPV, *concreteShape, transform, false); + if (!res.ok()) { + return res.error(); + } + + const auto& [el, surface] = res.value(); + + return Result>::success(surface); + } + return Result>::failure( + GeoModelConversionError::WrongShapeForConverter); + } + return Result>::failure( + GeoModelConversionError::MissingLogicalVolume); + } +}; + +} // namespace Acts::detail diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoBoxConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoBoxConverter.hpp new file mode 100644 index 00000000000..1ed6f59950d --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoBoxConverter.hpp @@ -0,0 +1,39 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +#include + +namespace Acts { + +namespace detail { +struct GeoBoxConverter { + /// @brief Convert a GeoBox to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// @param geoBox The GeoBox to convert + /// @param absTransform from the GeoPhysVol + /// @param bool sensitive + /// + /// @return The detector element and surface + Result operator()(const GeoFullPhysVol& geoFPV, + const GeoBox& geoBox, + const Transform3& absTransform, + bool sensitive) const; +}; +} // namespace detail + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoIntersectionAnnulusConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoIntersectionAnnulusConverter.hpp new file mode 100644 index 00000000000..3e79d9b116d --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoIntersectionAnnulusConverter.hpp @@ -0,0 +1,38 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +#include + +namespace Acts { + +namespace detail { +struct GeoIntersectionAnnulusConverter { + /// @brief Convert a GeoBox to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// @param geoIntersection The GeoIntersection to convert + /// @param absTransform from the GeoPhysVol + /// @param bool sensitive + /// + /// @return The detector element and surface + Result operator()( + const GeoFullPhysVol& geoFPV, const GeoShapeIntersection& geoIntersection, + const Transform3& absTransform, bool sensitive) const; +}; +} // namespace detail + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoShiftConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoShiftConverter.hpp new file mode 100644 index 00000000000..96be34f6144 --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoShiftConverter.hpp @@ -0,0 +1,39 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" + +#include +#include + +#include + +namespace Acts { + +namespace detail { + +struct GeoShiftConverter { + /// @brief Convert a GeoBox to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// @param geoBox The GeoBox to convert + /// @param absTransform from the GeoPhysVol + /// @param bool sensitive + /// + /// @return The detector element and surface + Result operator()(const GeoFullPhysVol& geoFPV, + const GeoShapeShift& geoShift, + const Transform3& absTransform, + bool sensitive) const; +}; +} // namespace detail + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoTrdConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoTrdConverter.hpp new file mode 100644 index 00000000000..c5fb8ae48fc --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoTrdConverter.hpp @@ -0,0 +1,44 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +#include + +class GeoFullPhysVol; +class GeoTrd; + +namespace Acts { + +class Surface; + +namespace detail { +struct GeoTrdConverter { + /// @brief Convert a GeoTrd to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// @param geoTrd The GeoTrd to convert + /// @param absTransform from the GeoPhysVol + /// @param bool sensitive + /// + /// @return The detector element and surface + Result operator()(const GeoFullPhysVol& geoFPV, + const GeoTrd& geoTrd, + const Transform3& absTransform, + bool sensitive) const; +}; +} // namespace detail + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoTubeConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoTubeConverter.hpp new file mode 100644 index 00000000000..c98b069777d --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoTubeConverter.hpp @@ -0,0 +1,45 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +#include + +class GeoFullPhysVol; +class GeoTube; + +namespace Acts { + +namespace detail { +struct GeoTubeConverter { + Surface::SurfaceType targetShape = Surface::SurfaceType::Straw; + + /// @brief Convert a GeoTube to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// @param geoTube The GeoTube shape to convert + /// @param absTransform from the GeoPhysVol + /// @param bool sensitive + /// + /// @return The detector element and surface + Result operator()(const GeoFullPhysVol& geoFPV, + const GeoTube& geoTube, + const Transform3& absTransform, + bool sensitive) const; +}; +} // namespace detail + +} // namespace Acts diff --git a/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoUnionDoubleTrdConverter.hpp b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoUnionDoubleTrdConverter.hpp new file mode 100644 index 00000000000..c21f40a9f76 --- /dev/null +++ b/Plugins/GeoModel/include/Acts/Plugins/GeoModel/detail/GeoUnionDoubleTrdConverter.hpp @@ -0,0 +1,46 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/Result.hpp" + +#include +#include + +#include + +class GeoFullPhysVol; +class GeoTube; + +namespace Acts { + +namespace detail { +struct GeoUnionDoubleTrdConverter { + /// Merge trapezoids up to this gap + double gapTolerance = 0.2; + + /// @brief Convert a GeoTube to a detector element and surface + /// + /// @param geoFPV The full physical volume to convert (contains shape) + /// @param geoTube The GeoTube shape to convert + /// @param absTransform from the GeoPhysVol + /// @param bool sensitive + /// + /// @return The detector element and surface + Result operator()(const GeoFullPhysVol& geoFPV, + const GeoShapeUnion& geoTube, + const Transform3& absTransform, + bool sensitive) const; +}; +} // namespace detail + +} // namespace Acts diff --git a/Plugins/GeoModel/src/GeoModelConversionError.cpp b/Plugins/GeoModel/src/GeoModelConversionError.cpp new file mode 100644 index 00000000000..c435ecb722a --- /dev/null +++ b/Plugins/GeoModel/src/GeoModelConversionError.cpp @@ -0,0 +1,45 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" + +#include + +namespace { + +class GeoModelConversionErrorCategory : public std::error_category { + public: + // Return a short descriptive name for the category. + const char* name() const noexcept final { return "GeoModelConversionError"; } + + // Return what each enum means in text. + std::string message(int c) const final { + using Acts::GeoModelConversionError; + + switch (static_cast(c)) { + case GeoModelConversionError::WrongShapeForConverter: + return "Wrong shape provided for this converter"; + case GeoModelConversionError::InvalidShapeParameters: + return "Shape parameters can not be converted to Surface " + "representation"; + case GeoModelConversionError::UnkownShape: + return "Unknown Shape provided, no converter available"; + case GeoModelConversionError::MissingLogicalVolume: + return "No logical volume found for the shape"; + default: + return "unknown"; + } + } +}; + +} // namespace + +std::error_code Acts::make_error_code(Acts::GeoModelConversionError e) { + static GeoModelConversionErrorCategory c; + return {static_cast(e), c}; +} diff --git a/Plugins/GeoModel/src/GeoModelDetectorElement.cpp b/Plugins/GeoModel/src/GeoModelDetectorElement.cpp new file mode 100644 index 00000000000..fb2894cbd4a --- /dev/null +++ b/Plugins/GeoModel/src/GeoModelDetectorElement.cpp @@ -0,0 +1,42 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" + +#include "Acts/Surfaces/Surface.hpp" + +#include + +Acts::GeoModelDetectorElement::GeoModelDetectorElement( + const GeoFullPhysVol& geoPhysVol, std::shared_ptr surface, + const Transform3& sfTransform, ActsScalar thickness) + : m_geoPhysVol(&geoPhysVol), + m_surface(std::move(surface)), + m_surfaceTransform(sfTransform), + m_thickness(thickness) {} + +const Acts::Transform3& Acts::GeoModelDetectorElement::transform( + const GeometryContext& /*gctx*/) const { + return m_surfaceTransform; +} + +const Acts::Surface& Acts::GeoModelDetectorElement::surface() const { + return *m_surface; +} + +Acts::Surface& Acts::GeoModelDetectorElement::surface() { + return *m_surface; +} + +Acts::ActsScalar Acts::GeoModelDetectorElement::thickness() const { + return m_thickness; +} + +const GeoFullPhysVol& Acts::GeoModelDetectorElement::physicalVolume() const { + return *m_geoPhysVol; +} diff --git a/Plugins/GeoModel/src/GeoModelDetectorSurfaceFactory.cpp b/Plugins/GeoModel/src/GeoModelDetectorSurfaceFactory.cpp new file mode 100644 index 00000000000..f2250828d58 --- /dev/null +++ b/Plugins/GeoModel/src/GeoModelDetectorSurfaceFactory.cpp @@ -0,0 +1,142 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/GeoModelDetectorSurfaceFactory.hpp" + +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Plugins/GeoModel/GeoModelTree.hpp" + +#include + +#include +#include + +namespace { +std::string recType(const GeoShapeShift &gshift); +std::string recType(const GeoShapeUnion &gunion); +std::string recType(const GeoShape &gshape); + +std::string recType(const GeoShapeShift &gshift) { + return "Shift[" + recType(*gshift.getOp()) + "]"; +} +std::string recType(const GeoShapeUnion &gunion) { + return "Union[" + recType(*gunion.getOpA()) + ", " + + recType(*gunion.getOpB()) + "]"; +} +std::string recType(const GeoShape &gshape) { + if (auto ptr = dynamic_cast(&gshape); ptr != nullptr) { + return recType(*ptr); + } + if (auto ptr = dynamic_cast(&gshape); ptr != nullptr) { + return recType(*ptr); + } + return gshape.type(); +} +} // namespace + +Acts::GeoModelDetectorSurfaceFactory::GeoModelDetectorSurfaceFactory( + const Config &cfg, std::unique_ptr mlogger) + : m_cfg(cfg), m_logger(std::move(mlogger)) {} + +void Acts::GeoModelDetectorSurfaceFactory::construct( + Cache &cache, const GeometryContext &, const GeoModelTree &geoModelTree, + const Options &options) { + if (geoModelTree.geoReader == nullptr) { + throw std::invalid_argument("GeoModelTree has no GeoModelReader"); + } + + for (const auto &q : options.queries) { + ACTS_VERBOSE("Constructing detector elements for query " << q); + auto qFPV = geoModelTree.geoReader + ->getPublishedNodes(q); + + auto matches = [&](const std::string &name, const GeoVFullPhysVol &fpv) { + bool match = m_cfg.nameList.empty() && m_cfg.materialList.empty(); + if (match) { + return true; + } + + match |= std::any_of( + m_cfg.nameList.begin(), m_cfg.nameList.end(), + [&](const auto &n) { return name.find(n) != std::string::npos; }); + if (match) { + return true; + } + + const auto &matStr = fpv.getLogVol()->getMaterial()->getName(); + match |= std::any_of( + m_cfg.materialList.begin(), m_cfg.materialList.end(), + [&](const auto &m) { return matStr.find(m) != std::string::npos; }); + + return match; + }; + + // Store stems of volume names and materials for INFO output + std::set volNameStems; + std::set materials; + + for (auto &[name, fpv] : qFPV) { + if (fpv == nullptr) { + ACTS_WARNING("Pointer to volume '" << name << "' is null"); + continue; + } + + const std::string &vname = fpv->getLogVol()->getName(); + const GeoShape &shape = *fpv->getLogVol()->getShape(); + + if (!matches(name, *fpv)) { + continue; + } + + // Convert using the list of converters + bool success = false; + + for (const auto &converter : m_cfg.shapeConverters) { + auto converted = converter->toSensitiveSurface(*fpv); + if (converted.ok()) { + // Add the element and surface to the cache + cache.sensitiveSurfaces.push_back(converted.value()); + const auto &[el, sf] = converted.value(); + success = true; + ACTS_VERBOSE("successfully converted " + << name << " (" << vname << " / " << recType(shape) + << " / " << fpv->getLogVol()->getMaterial()->getName() + << ")"); + if (!(el && sf)) { + throw std::runtime_error("something is nullptr"); + } + break; + } + } + + if (!success) { + ACTS_DEBUG(name << " (" << vname << " / " << recType(shape) + << ") could not be converted by any converter"); + } else { + volNameStems.emplace(name.substr(0, 6)); + materials.emplace(fpv->getLogVol()->getMaterial()->getName()); + } + } + ACTS_VERBOSE("Found " << qFPV.size() + << " full physical volumes matching the query."); + + auto streamVec = [](const auto &v) { + std::stringstream ss; + for (const auto &el : v) { + ss << el << " "; + } + return ss.str(); + }; + + ACTS_INFO("Converted volumes (stems): " << streamVec(volNameStems)); + ACTS_INFO("Materials of converted volumes: " << streamVec(materials)); + } + ACTS_DEBUG("Constructed " + << cache.sensitiveSurfaces.size() << " sensitive elements and " + << cache.passiveSurfaces.size() << " passive elements"); +} diff --git a/Plugins/GeoModel/src/detail/GeoBoxConverter.cpp b/Plugins/GeoModel/src/detail/GeoBoxConverter.cpp new file mode 100644 index 00000000000..a1c65c3564a --- /dev/null +++ b/Plugins/GeoModel/src/detail/GeoBoxConverter.cpp @@ -0,0 +1,72 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/detail/GeoBoxConverter.hpp" + +#include "Acts/Definitions/Common.hpp" +#include "Acts/Definitions/Units.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/Surface.hpp" + +#include +#include +#include +#include +#include + +Acts::Result +Acts::detail::GeoBoxConverter::operator()(const GeoFullPhysVol& geoFPV, + const GeoBox& geoBox, + const Transform3& absTransform, + bool sensitive) const { + /// auto-calculate the unit length conversion + static constexpr ActsScalar unitLength = + Acts::UnitConstants::mm / GeoModelKernelUnits::millimeter; + + // Create the surface transform + Transform3 transform = Transform3::Identity(); + transform.translation() = unitLength * absTransform.translation(); + auto rotation = absTransform.rotation(); + // Get the half lengths + std::vector halfLengths = {geoBox.getXHalfLength(), + geoBox.getYHalfLength(), + geoBox.getZHalfLength()}; + // Create the surface + auto minElement = std::min_element(halfLengths.begin(), halfLengths.end()); + auto zIndex = std::distance(halfLengths.begin(), minElement); + std::size_t yIndex = zIndex > 0u ? zIndex - 1u : 2u; + std::size_t xIndex = yIndex > 0u ? yIndex - 1u : 2u; + + Vector3 colX = rotation.col(xIndex); + Vector3 colY = rotation.col(yIndex); + Vector3 colZ = rotation.col(zIndex); + rotation.col(0) = colX; + rotation.col(1) = colY; + rotation.col(2) = colZ; + transform.linear() = rotation; + + // Create the surface bounds + ActsScalar halfX = unitLength * halfLengths[xIndex]; + ActsScalar halfY = unitLength * halfLengths[yIndex]; + auto rectangleBounds = std::make_shared(halfX, halfY); + if (!sensitive) { + auto surface = + Surface::makeShared(transform, rectangleBounds); + return std::make_tuple(nullptr, surface); + } + // Create the element and the surface + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, rectangleBounds, transform, + 2 * unitLength * halfLengths[zIndex]); + auto surface = detectorElement->surface().getSharedPtr(); + // Return the detector element and surface + return std::make_tuple(detectorElement, surface); +} diff --git a/Plugins/GeoModel/src/detail/GeoIntersectionAnnulusConverter.cpp b/Plugins/GeoModel/src/detail/GeoIntersectionAnnulusConverter.cpp new file mode 100644 index 00000000000..5fdaf563db6 --- /dev/null +++ b/Plugins/GeoModel/src/detail/GeoIntersectionAnnulusConverter.cpp @@ -0,0 +1,97 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/detail/GeoIntersectionAnnulusConverter.hpp" + +#include "Acts/Definitions/Common.hpp" +#include "Acts/Definitions/Units.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Surfaces/AnnulusBounds.hpp" +#include "Acts/Surfaces/DiscSurface.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Surfaces/detail/AnnulusBoundsHelper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +Acts::Result +Acts::detail::GeoIntersectionAnnulusConverter::operator()( + const GeoFullPhysVol& geoFPV, const GeoShapeIntersection& geoIntersection, + const Transform3& absTransform, bool sensitive) const { + /// auto-calculate the unit length conversion + static constexpr ActsScalar unitLength = + Acts::UnitConstants::mm / GeoModelKernelUnits::millimeter; + + // Returns the first operand being ANDed + const GeoShape* opA = geoIntersection.getOpA(); + const GeoShape* opB = geoIntersection.getOpB(); + + const GeoTubs* tubs = dynamic_cast(opA); + if (tubs != nullptr) { + // rMin, rMax + ActsScalar rMin = unitLength * tubs->getRMin(); + ActsScalar rMax = unitLength * tubs->getRMax(); + + // Get the shift + const GeoShapeShift* shapeShift = dynamic_cast(opB); + if (shapeShift != nullptr) { + const Transform3& shift = shapeShift->getX(); + const GeoGenericTrap* trap = + dynamic_cast(shapeShift->getOp()); + if (trap != nullptr) { + ActsScalar thickness = 2 * unitLength * trap->getZHalfLength(); + // X half length at -z, -y. + auto trapVertices = trap->getVertices(); + + std::vector faceVertices(trapVertices.begin(), + trapVertices.begin() + 4u); + // to make sure they are in the right order + std::sort(faceVertices.begin(), faceVertices.end(), + [](const auto& a, const auto& b) { + return (VectorHelpers::phi(a) > VectorHelpers::phi(b)); + }); + + // Turn them into global + std::vector faceVertices3D; + for (const auto& v : faceVertices) { + faceVertices3D.push_back( + shift * Vector3{unitLength * v.x(), unitLength * v.y(), 0.}); + } + + faceVertices.clear(); + for (auto& v : faceVertices3D) { + faceVertices.push_back(v.block<2, 1>(0, 0)); + } + + auto [annulusBounds, annulusTransform] = + Acts::detail::AnnulusBoundsHelper::create(absTransform, rMin, rMax, + faceVertices); + if (!sensitive) { + auto surface = + Surface::makeShared(annulusTransform, annulusBounds); + return std::make_tuple(nullptr, surface); + } + + // Create the detector element + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, annulusBounds, annulusTransform, thickness); + auto surface = detectorElement->surface().getSharedPtr(); + return std::make_tuple(detectorElement, surface); + } + return std::make_tuple(nullptr, nullptr); + } + return std::make_tuple(nullptr, nullptr); + } + return std::make_tuple(nullptr, nullptr); +} diff --git a/Plugins/GeoModel/src/detail/GeoShiftConverter.cpp b/Plugins/GeoModel/src/detail/GeoShiftConverter.cpp new file mode 100644 index 00000000000..99cd173ba5b --- /dev/null +++ b/Plugins/GeoModel/src/detail/GeoShiftConverter.cpp @@ -0,0 +1,89 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/detail/GeoShiftConverter.hpp" + +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoBoxConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoTrdConverter.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoTubeConverter.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/StrawSurface.hpp" +#include "Acts/Surfaces/TrapezoidBounds.hpp" + +namespace Acts::detail { + +template +Result impl(const GeoFullPhysVol& geoFPV, + const GeoShapeShift& geoShift, + const Transform3& absTransform, + bool sensitive) { + auto trd = dynamic_cast(geoShift.getOp()); + + if (trd == nullptr) { + return GeoModelConversionError::WrongShapeForConverter; + ; + } + + const Transform3& shift = geoShift.getX(); + + const auto& conversionRes = + Converter{}(geoFPV, *trd, absTransform * shift, sensitive); + if (!conversionRes.ok()) { + return conversionRes.error(); + } + auto [el, surface] = conversionRes.value(); + + // Use knowledge from GeoTrdConverter to make shared bounds object + const auto& bounds = static_cast(surface->bounds()); + auto sharedBounds = std::make_shared(bounds); + + // TODO this procedure could be stripped from all converters because it is + // pretty generic + if (!sensitive) { + auto newSurface = Surface::template makeShared( + surface->transform({}), sharedBounds); + return std::make_tuple(nullptr, newSurface); + } + + auto newEl = GeoModelDetectorElement::createDetectorElement( + el->physicalVolume(), sharedBounds, el->transform({}), el->thickness()); + auto newSurface = newEl->surface().getSharedPtr(); + return std::make_tuple(newEl, newSurface); +} + +Result GeoShiftConverter::operator()( + const GeoFullPhysVol& geoFPV, const GeoShapeShift& geoShift, + const Transform3& absTransform, bool sensitive) const { + auto r = impl( + geoFPV, geoShift, absTransform, sensitive); + + if (r.ok()) { + return r; + } + + r = impl( + geoFPV, geoShift, absTransform, sensitive); + + if (r.ok()) { + return r; + } + + // For now this does straw by default + r = impl( + geoFPV, geoShift, absTransform, sensitive); + + if (r.ok()) { + return r; + } + + return GeoModelConversionError::WrongShapeForConverter; +} + +} // namespace Acts::detail diff --git a/Plugins/GeoModel/src/detail/GeoTrdConverter.cpp b/Plugins/GeoModel/src/detail/GeoTrdConverter.cpp new file mode 100644 index 00000000000..9ba9cb29bed --- /dev/null +++ b/Plugins/GeoModel/src/detail/GeoTrdConverter.cpp @@ -0,0 +1,101 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/detail/GeoTrdConverter.hpp" + +#include "Acts/Definitions/Common.hpp" +#include "Acts/Definitions/Units.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Surfaces/TrapezoidBounds.hpp" + +#include +#include +#include +#include +#include +#include + +Acts::Result +Acts::detail::GeoTrdConverter::operator()(const GeoFullPhysVol& geoFPV, + const GeoTrd& geoTrd, + const Transform3& absTransform, + bool sensitive) const { + /// auto-calculate the unit length conversion + static constexpr ActsScalar unitLength = + Acts::UnitConstants::mm / GeoModelKernelUnits::millimeter; + + // Create the surface transform + Transform3 transform = Transform3::Identity(); + transform.translation() = unitLength * absTransform.translation(); + + // GeoTrd is defined that halfZ needs to map onto surface halfY + // Create the surface + ActsScalar halfX1 = geoTrd.getXHalfLength1(); + ActsScalar halfX2 = geoTrd.getXHalfLength2(); + ActsScalar halfY1 = geoTrd.getYHalfLength1(); + ActsScalar halfY2 = geoTrd.getYHalfLength2(); + ActsScalar halfZ = geoTrd.getZHalfLength(); + + // The diffs + ActsScalar diffX = std::abs(halfX2 - halfX1); + ActsScalar diffY = std::abs(halfY2 - halfY1); + + // If both X and Y are trapezoidal - consider + if (diffX > 2 * std::numeric_limits::epsilon() && + diffY > 2 * std::numeric_limits::epsilon()) { + throw std::invalid_argument( + "GeoModelSurfaceConverter: GeoTrd conversion to Trapezoid " + "ambiguous."); + } + + // And its interpretation + ActsScalar minHalfX = unitLength * (diffX > diffY ? halfX1 : halfY1); + ActsScalar maxHalfX = unitLength * (diffX > diffY ? halfX2 : halfY2); + ActsScalar thickness = unitLength * (diffX > diffY ? diffY : diffX); + + // This is a convention of the TrapezoidBounds + int swapZ = (maxHalfX < minHalfX) ? -1 : 1; + if (swapZ < 0) { + std::swap(minHalfX, maxHalfX); + } + + // Adjust the rotation matrix + RotationMatrix3 trotation = absTransform.rotation(); + + if (diffX > diffY) { + // Rotation is x, z, y ... acyclic, hence the sign change + trotation.col(1) = swapZ * absTransform.rotation().col(2); + trotation.col(2) = -swapZ * absTransform.rotation().col(1); + } else { + // Rotation is y, z, x ... cyclic, hence no sign change + trotation.col(0) = absTransform.rotation().col(1); + trotation.col(1) = swapZ * absTransform.rotation().col(2); + trotation.col(2) = swapZ * absTransform.rotation().col(0); + } + transform.linear() = trotation; + + auto trapezoidBounds = + std::make_shared(minHalfX, maxHalfX, halfZ); + // std::cout << " TrapezoidBounds: minHalfX=" << minHalfX << ", maxHalfX=" + // << maxHalfX << ", halfz=" << halfZ << std::endl; + if (!sensitive) { + auto surface = + Surface::makeShared(transform, trapezoidBounds); + return std::make_tuple(nullptr, surface); + } + + // Create the element and the surface + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, trapezoidBounds, transform, thickness); + auto surface = detectorElement->surface().getSharedPtr(); + return std::make_tuple(detectorElement, surface); +} diff --git a/Plugins/GeoModel/src/detail/GeoTubeConverter.cpp b/Plugins/GeoModel/src/detail/GeoTubeConverter.cpp new file mode 100644 index 00000000000..c63ca616667 --- /dev/null +++ b/Plugins/GeoModel/src/detail/GeoTubeConverter.cpp @@ -0,0 +1,89 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/detail/GeoTubeConverter.hpp" + +#include "Acts/Definitions/Common.hpp" +#include "Acts/Definitions/Units.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Surfaces/CylinderBounds.hpp" +#include "Acts/Surfaces/CylinderSurface.hpp" +#include "Acts/Surfaces/DiscSurface.hpp" +#include "Acts/Surfaces/LineBounds.hpp" +#include "Acts/Surfaces/RadialBounds.hpp" +#include "Acts/Surfaces/StrawSurface.hpp" +#include "Acts/Surfaces/Surface.hpp" + +#include +#include +#include +#include +#include + +Acts::Result +Acts::detail::GeoTubeConverter::operator()(const GeoFullPhysVol& geoFPV, + const GeoTube& geoTube, + const Transform3& absTransform, + bool sensitive) const { + /// auto-calculate the unit length conversion + static constexpr ActsScalar unitLength = + Acts::UnitConstants::mm / GeoModelKernelUnits::millimeter; + + // Create the surface transform + Transform3 transform = Transform3::Identity(); + transform.translation() = unitLength * absTransform.translation(); + transform.linear() = absTransform.rotation(); + + // Create the surface + ActsScalar innerRadius = unitLength * geoTube.getRMin(); + ActsScalar outerRadius = unitLength * geoTube.getRMax(); + ActsScalar halfZ = unitLength * geoTube.getZHalfLength(); + + if (targetShape == Surface::SurfaceType::Straw) { + // Create the element and the surface + auto lineBounds = std::make_shared(outerRadius, halfZ); + if (!sensitive) { + auto surface = Surface::makeShared(transform, lineBounds); + return std::make_tuple(nullptr, surface); + } + + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, lineBounds, transform, 2 * outerRadius); + auto surface = detectorElement->surface().getSharedPtr(); + return std::make_tuple(detectorElement, surface); + // Next option is translation to disc + } else if (targetShape == Surface::SurfaceType::Disc) { + auto radialBounds = + std::make_shared(innerRadius, outerRadius); + if (!sensitive) { + auto surface = Surface::makeShared(transform, radialBounds); + return std::make_tuple(nullptr, surface); + } + + // Create the element and the surface + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, radialBounds, transform, 2 * halfZ); + auto surface = detectorElement->surface().getSharedPtr(); + return std::make_tuple(detectorElement, surface); + } + // Finally cylinder to cylinder + auto cylinderBounds = std::make_shared(outerRadius, halfZ); + if (!sensitive) { + auto surface = + Surface::makeShared(transform, cylinderBounds); + return std::make_tuple(nullptr, surface); + } + // Create the element and the surface + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, cylinderBounds, transform, outerRadius - innerRadius); + auto surface = detectorElement->surface().getSharedPtr(); + return std::make_tuple(detectorElement, surface); +} diff --git a/Plugins/GeoModel/src/detail/GeoUnionDoubleTrdConverter.cpp b/Plugins/GeoModel/src/detail/GeoUnionDoubleTrdConverter.cpp new file mode 100644 index 00000000000..bd4f614850b --- /dev/null +++ b/Plugins/GeoModel/src/detail/GeoUnionDoubleTrdConverter.cpp @@ -0,0 +1,154 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/GeoModel/detail/GeoUnionDoubleTrdConverter.hpp" + +#include "Acts/Plugins/GeoModel/GeoModelConversionError.hpp" +#include "Acts/Plugins/GeoModel/detail/GeoShiftConverter.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/TrapezoidBounds.hpp" + +namespace { + +auto distanceLinePoint(const Acts::Vector3 &lineA, const Acts::Vector3 &lineB, + const Acts::Vector3 &p) { + auto dir = lineB - lineA; + auto ap = p - lineA; + return ap.cross(dir).norm() / dir.norm(); +} + +/// Checks with the following properties if the trapezoids are mergeable +bool trapezoidsAreMergeable(const std::vector &vtxsa, + const std::vector &vtxsb) { + // Compute the distance of the lines connecting A3 and B0 and the midpoint of + // the gap (resp. for other trapezoid side) These should be close to zero, + // otherwise we cannot merge the trapezoids + auto P1 = vtxsa[0] + 0.5 * (vtxsb[3] - vtxsa[0]); + auto dist1 = distanceLinePoint(vtxsa[3], vtxsb[0], P1); + + auto P2 = vtxsa[1] + 0.5 * (vtxsb[2] - vtxsa[1]); + auto dist2 = distanceLinePoint(vtxsa[2], vtxsb[1], P2); + + if (dist1 > 1.e-3 || dist2 > 1.e-3) { + return false; + } + + // We could verify other properties, but this seems sufficient for know + return true; +} + +} // namespace + +namespace Acts::detail { + +Result GeoUnionDoubleTrdConverter::operator()( + const GeoFullPhysVol &geoFPV, const GeoShapeUnion &geoUnion, + const Transform3 &absTransform, bool sensitive) const { + const auto shiftA = dynamic_cast(geoUnion.getOpA()); + const auto shiftB = dynamic_cast(geoUnion.getOpB()); + + if (!(shiftA && shiftB)) { + return GeoModelConversionError::WrongShapeForConverter; + } + + auto shiftARes = + detail::GeoShiftConverter{}(geoFPV, *shiftA, absTransform, sensitive); + if (!shiftARes.ok()) { + return shiftARes.error(); + } + auto shiftBRes = + detail::GeoShiftConverter{}(geoFPV, *shiftB, absTransform, sensitive); + if (!shiftBRes.ok()) { + return shiftBRes.error(); + } + + const auto &[elA, surfaceA] = shiftARes.value(); + const auto &[elB, surfaceB] = shiftBRes.value(); + + if (!(surfaceA && surfaceB)) { + return GeoModelConversionError::WrongShapeForConverter; + } + + if (surfaceA->bounds().type() != Acts::SurfaceBounds::eTrapezoid || + surfaceB->bounds().type() != Acts::SurfaceBounds::eTrapezoid) { + return GeoModelConversionError::WrongShapeForConverter; + } + + // At this point we know, that we have two trapezoids + // We assume the following Layout for this: + // + // 0 ______________________ 1 + // \ / + // \ B / + // \________________/ + // 3 2 + // 0 _____________ 1 + // \ / + // \ A / + // \_______/ + // 3 2 + + // First check now, if this actually is correct + const auto vtxsa = surfaceA->polyhedronRepresentation({}, 0).vertices; + const auto vtxsb = surfaceB->polyhedronRepresentation({}, 0).vertices; + + if (!trapezoidsAreMergeable(vtxsa, vtxsb)) { + return GeoModelConversionError::WrongShapeForConverter; + } + + // Compute half length y as the distance between the middle points of the + // outer lines of the trapezoids + Acts::Vector3 mpA = vtxsa[3] + 0.5 * (vtxsa[2] - vtxsa[3]); + Acts::Vector3 mpB = vtxsb[0] + 0.5 * (vtxsb[1] - vtxsb[0]); + + auto halfLengthY = 0.5 * (mpB - mpA).norm(); + + // Compute the gap between trapezoids + const auto &boundsA = + static_cast(surfaceA->bounds()); + const auto &boundsB = + static_cast(surfaceB->bounds()); + + const auto gap = + halfLengthY - (boundsA.values()[TrapezoidBounds::eHalfLengthY] + + boundsB.values()[TrapezoidBounds::eHalfLengthY]); + + if (gap > gapTolerance) { + return GeoModelConversionError::WrongShapeForConverter; + } + + // For the x half lengths, we can take the values from the 2 trapezoids + auto hlxny = boundsA.values()[TrapezoidBounds::eHalfLengthXposY]; + auto hlxpy = boundsB.values()[TrapezoidBounds::eHalfLengthXnegY]; + + auto trapezoidBounds = + std::make_shared(hlxpy, hlxny, halfLengthY); + + // Create transform from the transform of surfaceA and translate it in y + // direction using the half length + auto transform = surfaceA->transform({}); + transform.translate(Vector3{ + 0.f, boundsA.values()[TrapezoidBounds::eHalfLengthY] - halfLengthY, 0.f}); + + // TODO extract this code snipped since it is copied quite a lot + if (!sensitive) { + auto surface = + Surface::makeShared(transform, trapezoidBounds); + return std::make_tuple(nullptr, surface); + } + + // Create the element and the surface (we assume both have equal thickness) + auto detectorElement = + GeoModelDetectorElement::createDetectorElement( + geoFPV, trapezoidBounds, transform, elA->thickness()); + auto surface = detectorElement->surface().getSharedPtr(); + + return std::make_tuple(detectorElement, surface); +} + +} // namespace Acts::detail diff --git a/Tests/UnitTests/Plugins/CMakeLists.txt b/Tests/UnitTests/Plugins/CMakeLists.txt index 1df68a519a5..a3ce9f373ef 100644 --- a/Tests/UnitTests/Plugins/CMakeLists.txt +++ b/Tests/UnitTests/Plugins/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory_if(Cuda ACTS_BUILD_PLUGIN_CUDA) add_subdirectory_if(DD4hep ACTS_BUILD_PLUGIN_DD4HEP) add_subdirectory_if(ExaTrkX ACTS_BUILD_PLUGIN_EXATRKX) add_subdirectory_if(Geant4 ACTS_BUILD_PLUGIN_GEANT4) +add_subdirectory_if(GeoModel ACTS_BUILD_PLUGIN_GEOMODEL) add_subdirectory_if(Json ACTS_BUILD_PLUGIN_JSON) add_subdirectory_if(TGeo ACTS_BUILD_PLUGIN_TGEO) add_subdirectory_if(EDM4hep ACTS_BUILD_PLUGIN_EDM4HEP) diff --git a/Tests/UnitTests/Plugins/GeoModel/CMakeLists.txt b/Tests/UnitTests/Plugins/GeoModel/CMakeLists.txt new file mode 100644 index 00000000000..b20d8effc8e --- /dev/null +++ b/Tests/UnitTests/Plugins/GeoModel/CMakeLists.txt @@ -0,0 +1,5 @@ +set(unittest_extra_libraries ActsPluginGeoModel) + +add_unittest(GeoModelDetectorElement GeoModelDetectorElementTests.cpp) +add_unittest(GeoBoxConverter GeoBoxConverterTests.cpp) +add_unittest(GeoTrdConverter GeoTrdConverterTests.cpp) diff --git a/Tests/UnitTests/Plugins/GeoModel/GeoBoxConverterTests.cpp b/Tests/UnitTests/Plugins/GeoModel/GeoBoxConverterTests.cpp new file mode 100644 index 00000000000..0e9ed440ff2 --- /dev/null +++ b/Tests/UnitTests/Plugins/GeoModel/GeoBoxConverterTests.cpp @@ -0,0 +1,117 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConverters.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Surfaces/SurfaceBounds.hpp" +#include "Acts/Surfaces/TrapezoidBounds.hpp" +#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" + +#include +#include +#include +#include +#include +#include + +Acts::GeometryContext tContext; +Acts::RotationMatrix3 idRotation = Acts::RotationMatrix3::Identity(); +Acts::Transform3 idTransform = Acts::Transform3::Identity(); + +BOOST_AUTO_TEST_SUITE(GeoModelPlugin) + +// GeoBox conversion test case +BOOST_AUTO_TEST_CASE(GeoBoxToSensitiveConversion) { + auto material = new GeoMaterial("Material", 1.0); + // Let's create a GeoFullPhysVol object + + // (BOX object) - XY + auto boxXY = new GeoBox(100, 200, 2); + auto logXY = new GeoLogVol("LogVolumeXY", boxXY, material); + auto fphysXY = new GeoFullPhysVol(logXY); + + auto converted = Acts::GeoBoxConverter{}.toSensitiveSurface(*fphysXY); + + BOOST_CHECK(converted.ok()); + + auto [elementXY, surfaceXY] = converted.value(); + + BOOST_CHECK(surfaceXY->type() == Acts::Surface::SurfaceType::Plane); + + // Check the bounds + const Acts::RectangleBounds* rBoundsXY = + dynamic_cast(&(surfaceXY->bounds())); + BOOST_CHECK(rBoundsXY != nullptr); + CHECK_CLOSE_ABS(rBoundsXY->halfLengthX(), 100, 1e-6); + CHECK_CLOSE_ABS(rBoundsXY->halfLengthY(), 200, 1e-6); + + // Check the transform -> should be identity transform + const Acts::Transform3& transformXY = surfaceXY->transform(tContext); + BOOST_CHECK(transformXY.isApprox(idTransform)); + + // (BOX object) - YZ + auto boxYZ = new GeoBox(2, 200, 300); + auto logYZ = new GeoLogVol("LogVolumeYZ", boxYZ, material); + auto fphysYZ = new GeoFullPhysVol(logYZ); + + converted = Acts::GeoBoxConverter{}.toSensitiveSurface(*fphysYZ); + + BOOST_CHECK(converted.ok()); + + auto [elementYZ, surfaceYZ] = converted.value(); + + BOOST_CHECK(surfaceYZ->type() == Acts::Surface::SurfaceType::Plane); + const Acts::RectangleBounds* rBoundsYZ = + dynamic_cast(&(surfaceYZ->bounds())); + BOOST_CHECK(rBoundsYZ != nullptr); + CHECK_CLOSE_ABS(rBoundsYZ->halfLengthX(), 200, 1e-6); + CHECK_CLOSE_ABS(rBoundsYZ->halfLengthY(), 300, 1e-6); + + // Check the transform -> should be cyclic permutation of the identity + const Acts::Transform3& transformYZ = surfaceYZ->transform(tContext); + + Acts::RotationMatrix3 rotationYZ = transformYZ.rotation(); + BOOST_CHECK(rotationYZ.col(0).isApprox(idRotation.col(1))); + BOOST_CHECK(rotationYZ.col(1).isApprox(idRotation.col(2))); + BOOST_CHECK(rotationYZ.col(2).isApprox(idRotation.col(0))); + + // (BOX object) - XZ + auto boxXZ = new GeoBox(400, 2, 300); + auto logXZ = new GeoLogVol("LogVolumeXZ", boxXZ, material); + auto fphysXZ = new GeoFullPhysVol(logXZ); + + converted = Acts::GeoBoxConverter{}.toSensitiveSurface(*fphysXZ); + + BOOST_CHECK(converted.ok()); + + auto [elementXZ, surfaceXZ] = converted.value(); + + BOOST_CHECK(surfaceXZ->type() == Acts::Surface::SurfaceType::Plane); + + // Check the bounds + const Acts::RectangleBounds* rBoundsXZ = + dynamic_cast(&(surfaceXZ->bounds())); + + BOOST_CHECK(rBoundsXZ != nullptr); + CHECK_CLOSE_ABS(rBoundsXZ->halfLengthX(), 300, 1e-6); + CHECK_CLOSE_ABS(rBoundsXZ->halfLengthY(), 400, 1e-6); + + // Check the transform -> should be cyclic permutation of the identity + const Acts::Transform3& transformXZ = surfaceXZ->transform(tContext); + + Acts::RotationMatrix3 rotationXZ = transformXZ.rotation(); + BOOST_CHECK(rotationXZ.col(0).isApprox(idRotation.col(2))); + BOOST_CHECK(rotationXZ.col(1).isApprox(idRotation.col(0))); + BOOST_CHECK(rotationXZ.col(2).isApprox(idRotation.col(1))); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Plugins/GeoModel/GeoModelDetectorElementTests.cpp b/Tests/UnitTests/Plugins/GeoModel/GeoModelDetectorElementTests.cpp new file mode 100644 index 00000000000..1a5ae649990 --- /dev/null +++ b/Tests/UnitTests/Plugins/GeoModel/GeoModelDetectorElementTests.cpp @@ -0,0 +1,40 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Plugins/GeoModel/GeoModelDetectorElement.hpp" +#include "Acts/Surfaces/PlaneSurface.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Surfaces/Surface.hpp" + +#include +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(GeoModelPlugin) + +BOOST_AUTO_TEST_CASE(GeoModelDetectorElementConstruction) { + auto material = new GeoMaterial("Material", 1.0); + // Let's create a GeoFullPhysVol object + + // (BOX object) + auto boxXY = new GeoBox(100, 200, 2); + auto logXY = new GeoLogVol("LogVolumeXY", boxXY, material); + auto fphysXY = new GeoFullPhysVol(logXY); + auto rBounds = std::make_shared(100, 200); + auto elementXY = + Acts::GeoModelDetectorElement::createDetectorElement( + *fphysXY, rBounds, Acts::Transform3::Identity(), 2.0); + + const Acts::Surface& surface = elementXY->surface(); + BOOST_CHECK(surface.type() == Acts::Surface::SurfaceType::Plane); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Plugins/GeoModel/GeoTrdConverterTests.cpp b/Tests/UnitTests/Plugins/GeoModel/GeoTrdConverterTests.cpp new file mode 100644 index 00000000000..27a5727fb77 --- /dev/null +++ b/Tests/UnitTests/Plugins/GeoModel/GeoTrdConverterTests.cpp @@ -0,0 +1,183 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2024 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Plugins/GeoModel/GeoModelConverters.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Surfaces/SurfaceBounds.hpp" +#include "Acts/Surfaces/TrapezoidBounds.hpp" +#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" + +#include +#include +#include +#include + +Acts::GeometryContext tContext; +Acts::RotationMatrix3 idRotation = Acts::RotationMatrix3::Identity(); +Acts::Transform3 idTransform = Acts::Transform3::Identity(); + +BOOST_AUTO_TEST_SUITE(GeoModelPlugin) + +// GeoBox conversion test case +BOOST_AUTO_TEST_CASE(GeoTrfToSensitiveConversion) { + auto material = new GeoMaterial("Material", 1.0); + // Let's create a GeoFullPhysVol object + + // (Trapezoid object) - YZ + // GeoTrd (double XHalfLength1, double XHalfLength2, double YHalfLength1, + // double YHalfLength2, double ZHalfLength); + auto trapYZ = new GeoTrd(2, 2, 50, 80, 60); + auto logYZ = new GeoLogVol("LogVolumeYZ", trapYZ, material); + auto fphysYZ = new GeoFullPhysVol(logYZ); + + auto converted = Acts::GeoTrdConverter{}.toSensitiveSurface(*fphysYZ); + + BOOST_CHECK(converted.ok()); + + auto [elementYZ, surfaceYZ] = converted.value(); + + // Check the bounds + const Acts::TrapezoidBounds* tBoundsYZ = + dynamic_cast(&(surfaceYZ->bounds())); + + BOOST_CHECK(tBoundsYZ != nullptr); + CHECK_CLOSE_ABS( + tBoundsYZ->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXnegY), 50, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsYZ->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXposY), 80, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsYZ->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthY), 60, + 1e-6); + + // Check the transform -> should be cyclic permutation of the identity + const Acts::Transform3& transformYZ = surfaceYZ->transform(tContext); + + Acts::RotationMatrix3 rotationYZ = transformYZ.rotation(); + BOOST_CHECK(rotationYZ.col(0).isApprox(idRotation.col(1))); + BOOST_CHECK(rotationYZ.col(1).isApprox(idRotation.col(2))); + BOOST_CHECK(rotationYZ.col(2).isApprox(idRotation.col(0))); + + // (Trapezoid object) - YZ swapped + // GeoTrd (double XHalfLength1, double XHalfLength2, double YHalfLength1, + // double YHalfLength2, double ZHalfLength); + auto trapYZs = new GeoTrd(2, 2, 80, 50, 60); + auto logYZs = new GeoLogVol("LogVolumeYZs", trapYZs, material); + auto fphysYZs = new GeoFullPhysVol(logYZs); + + converted = Acts::GeoTrdConverter{}.toSensitiveSurface(*fphysYZs); + + BOOST_CHECK(converted.ok()); + + auto [elementYZs, surfaceYZs] = converted.value(); + + // Check the bounds + const Acts::TrapezoidBounds* tBoundsYZs = + dynamic_cast(&(surfaceYZs->bounds())); + + BOOST_CHECK(tBoundsYZs != nullptr); + CHECK_CLOSE_ABS( + tBoundsYZs->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXnegY), 50, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsYZs->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXposY), 80, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsYZs->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthY), 60, + 1e-6); + + // Check the transform -> should be cyclic permutation of the identity + const Acts::Transform3& transformYZs = surfaceYZs->transform(tContext); + + Acts::RotationMatrix3 rotationYZs = transformYZs.rotation(); + BOOST_CHECK(rotationYZs.col(0).isApprox(idRotation.col(1))); + BOOST_CHECK(rotationYZs.col(1).isApprox(-idRotation.col(2))); + BOOST_CHECK(rotationYZs.col(2).isApprox(-idRotation.col(0))); + + // (Trapezoid object) - XZ + auto trapXZ = new GeoTrd(50, 80, 2, 2, 60); + auto logXZ = new GeoLogVol("LogVolumeXZ", trapXZ, material); + auto fphysXZ = new GeoFullPhysVol(logXZ); + + converted = Acts::GeoTrdConverter{}.toSensitiveSurface(*fphysXZ); + + BOOST_CHECK(converted.ok()); + + auto [elementXZ, surfaceXZ] = converted.value(); + + // Check the bounds + const Acts::TrapezoidBounds* tBoundsXZ = + dynamic_cast(&(surfaceXZ->bounds())); + + BOOST_CHECK(tBoundsXZ != nullptr); + CHECK_CLOSE_ABS( + tBoundsXZ->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXnegY), 50, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsXZ->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXposY), 80, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsXZ->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthY), 60, + 1e-6); + + // Check the transform -> cyclic permuttation not possible + const Acts::Transform3& transformXZ = surfaceXZ->transform(tContext); + + Acts::RotationMatrix3 rotationXZ = transformXZ.rotation(); + BOOST_CHECK(rotationXZ.col(0).isApprox(idRotation.col(0))); + BOOST_CHECK(rotationXZ.col(1).isApprox(idRotation.col(2))); + BOOST_CHECK(rotationXZ.col(2).isApprox(-1 * idRotation.col(1))); + + // (Trapezoid object) - XZs (swapped) + auto trapXZs = new GeoTrd(80, 50, 2, 2, 60); + auto logXZs = new GeoLogVol("LogVolumeXZs", trapXZs, material); + auto fphysXZs = new GeoFullPhysVol(logXZs); + + converted = Acts::GeoTrdConverter{}.toSensitiveSurface(*fphysXZs); + + BOOST_CHECK(converted.ok()); + + auto [elementXZs, surfaceXZs] = converted.value(); + + // Check the bounds + const Acts::TrapezoidBounds* tBoundsXZs = + dynamic_cast(&(surfaceXZs->bounds())); + + BOOST_CHECK(tBoundsXZs != nullptr); + CHECK_CLOSE_ABS( + tBoundsXZs->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXnegY), 50, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsXZs->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthXposY), 80, + 1e-6); + CHECK_CLOSE_ABS( + tBoundsXZs->get(Acts::TrapezoidBounds::BoundValues::eHalfLengthY), 60, + 1e-6); + + // Check the transform -> cyclic permuttation not possible + const Acts::Transform3& transformXZs = surfaceXZs->transform(tContext); + + Acts::RotationMatrix3 rotationXZs = transformXZs.rotation(); + BOOST_CHECK(rotationXZs.col(0).isApprox(idRotation.col(0))); + BOOST_CHECK(rotationXZs.col(1).isApprox(-idRotation.col(2))); + BOOST_CHECK(rotationXZs.col(2).isApprox(idRotation.col(1))); + + // Double - trazoid -> throw exception + auto trapDouble = new GeoTrd(50, 80, 50, 80, 60); + auto logDouble = new GeoLogVol("LogVolumeDouble", trapDouble, material); + auto fphysDouble = new GeoFullPhysVol(logDouble); + + BOOST_CHECK_THROW(Acts::GeoTrdConverter{}.toSensitiveSurface(*fphysDouble), + std::invalid_argument); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/docs/plugins/geant4.md b/docs/plugins/geant4.md index 3463077500f..e7e3323c2dc 100644 --- a/docs/plugins/geant4.md +++ b/docs/plugins/geant4.md @@ -55,10 +55,11 @@ print('* Conversion yielded', len(ssurfaces)) drawContext = acts.GeometryContext() sensitiveRgb = [ 0, 150, 150 ] passiveRgb = [ 150, 150, 0] +segments = 64 # how many segments to approximate a full circle # Draw the sensitive surfaces -acts.examples.writeSurfacesObj(ssurfaces, drawContext, sensitiveRgb, 'detector-sensitives.obj') +acts.examples.writeSurfacesObj(ssurfaces, drawContext, sensitiveRgb, segments, 'detector-sensitives.obj') # Draw the passive surfaces -acts.examples.writeSurfacesObj(psurfaces, drawContext, passiveRgb, 'detector-passives.obj') +acts.examples.writeSurfacesObj(psurfaces, drawContext, passiveRgb, segments, 'detector-passives.obj') ``` ## Building a Detector from Geant4 input diff --git a/thirdparty/GeoModel/CMakeLists.txt b/thirdparty/GeoModel/CMakeLists.txt index 90bb89b06ba..d61f948be2e 100644 --- a/thirdparty/GeoModel/CMakeLists.txt +++ b/thirdparty/GeoModel/CMakeLists.txt @@ -15,14 +15,13 @@ message( STATUS "Building GeoModel as part of the ACTS project" ) set( GEOMODEL_VERSION "${_acts_geomodel_version}") -set( GEOMODEL_SETUP_JSON OFF CACHE BOOL "Skip setting up json completely" ) +set(GEOMODEL_SETUP_JSON OFF CACHE BOOL "Skip setting up json completely" ) -set ( GEOMODEL_BUILD_GEOMODELG4 "${ACTS_BUILD_PLUGIN_GEANT4}" CACHE BOOL "Build the Geant4 plugin" ) +set(GEOMODEL_BUILD_GEOMODELG4 "${ACTS_BUILD_PLUGIN_GEANT4}" CACHE BOOL "Build the Geant4 plugin" ) # Declare where to get geomodel from. -FetchContent_Declare( geomodel ${ACTS_GEOMODEL_SOURCE} - PATCH_COMMAND git am ${CMAKE_CURRENT_SOURCE_DIR}/0001-Add-option-to-skip-setting-up-json-completely.patch -) +FetchContent_Declare(geomodel ${ACTS_GEOMODEL_SOURCE} + PATCH_COMMAND git am ${CMAKE_CURRENT_SOURCE_DIR}/0001-Add-option-to-skip-setting-up-json-completely.patch) # Now set up its build. FetchContent_MakeAvailable( geomodel )