From 8f0ae3cbbecafb1a295719fc25949f8e1c6d2589 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Mon, 7 Oct 2024 12:05:10 +0200 Subject: [PATCH] refactor(geo): Geometry visualization update (#3681) Blocked by: - https://github.com/acts-project/acts/pull/3675 Related to #3502 --- The most significant changes include adding visualization methods to `TrackingGeometry` and `TrackingVolume`, updating the `Surface` class, and enhancing the `IVisualization3D` interface. Additionally, the `ObjVisualization3D` class has been refactored, and new default view configurations have been defined. ### Visualization Enhancements: * [`TrackingGeometry.hpp`](diffhunk://#diff-cc497a4ee5032615db90ef065e9466279a0fde38fbd201840a32d783d3d6ec4aR148-R158): Added a `visualize` method to enable visualization of tracking geometry, including substructures. (`[Core/include/Acts/Geometry/TrackingGeometry.hppR148-R158](diffhunk://#diff-cc497a4ee5032615db90ef065e9466279a0fde38fbd201840a32d783d3d6ec4aR148-R158)`) * [`TrackingVolume.hpp`](diffhunk://#diff-835fb549fb77cdaa632a4e2131c8dc17ad4973ab3f1600a4608582b706a3f68eR326-R344): Added methods to visualize tracking volumes and manage surfaces, including `visualize`, `surfaces`, and `addSurface`. (`[[1]](diffhunk://#diff-835fb549fb77cdaa632a4e2131c8dc17ad4973ab3f1600a4608582b706a3f68eR326-R344)`, `[[2]](diffhunk://#diff-835fb549fb77cdaa632a4e2131c8dc17ad4973ab3f1600a4608582b706a3f68eR477-R487)`, `[[3]](diffhunk://#diff-835fb549fb77cdaa632a4e2131c8dc17ad4973ab3f1600a4608582b706a3f68eR550)`) ### Interface Updates: * [`IVisualization3D.hpp`](diffhunk://#diff-c56ccee98d351daf6ac595cd265d30716a594ceb74030138411595aca66fdc39R75-R80): Introduced a destructor and a new `object` method to start a new object context. (`[Core/include/Acts/Visualization/IVisualization3D.hppR75-R80](diffhunk://#diff-c56ccee98d351daf6ac595cd265d30716a594ceb74030138411595aca66fdc39R75-R80)`) * [`ObjVisualization3D.hpp`](diffhunk://#diff-fab69ace861a3e5d8fc173e8627732777a5fdd52703e9e162a14db563ea56a2aL29-R32): Refactored to use `double` as the value type and added support for object contexts. (`[[1]](diffhunk://#diff-fab69ace861a3e5d8fc173e8627732777a5fdd52703e9e162a14db563ea56a2aL29-R32)`, `[[2]](diffhunk://#diff-fab69ace861a3e5d8fc173e8627732777a5fdd52703e9e162a14db563ea56a2aR76-R102)`) ### Configuration and Defaults: * [`ViewConfig.hpp`](diffhunk://#diff-e403e98244e75d6b7d32864417c37619de6becee73f53415b733073f655f0929R114-R117): Defined new default view configurations for surfaces, portals, volumes, and other elements. (`[[1]](diffhunk://#diff-e403e98244e75d6b7d32864417c37619de6becee73f53415b733073f655f0929R114-R117)`, `[[2]](diffhunk://#diff-e403e98244e75d6b7d32864417c37619de6becee73f53415b733073f655f0929R139-R146)`) ### Codebase Cleanup: * Removed obsolete and redundant code from `ObjVisualization3D.ipp`. (`[Core/include/Acts/Visualization/detail/ObjVisualization3D.ippL1-L179](diffhunk://#diff-29bbb6e8cfcb388c51d1e88237dd0c7d582762d7e05a0ec12278bdc336b0aaa1L1-L179)`) --- .../Acts/Geometry/TrackingGeometry.hpp | 11 + Core/include/Acts/Geometry/TrackingVolume.hpp | 12 + Core/include/Acts/Geometry/Volume.hpp | 2 +- Core/include/Acts/Surfaces/Surface.hpp | 2 +- .../Acts/Visualization/GeometryView3D.hpp | 6 - .../Acts/Visualization/IVisualization3D.hpp | 6 + .../Acts/Visualization/ObjVisualization3D.hpp | 44 ++-- .../Acts/Visualization/PlyVisualization3D.hpp | 4 + .../include/Acts/Visualization/ViewConfig.hpp | 45 +++- .../detail/ObjVisualization3D.ipp | 179 --------------- Core/src/Geometry/TrackingGeometry.cpp | 8 + Core/src/Geometry/TrackingVolume.cpp | 21 ++ Core/src/Visualization/CMakeLists.txt | 5 +- Core/src/Visualization/GeometryView3D.cpp | 10 - Core/src/Visualization/ObjVisualization3D.cpp | 211 ++++++++++++++++++ Examples/Python/python/acts/examples/odd.py | 3 +- Examples/Python/src/Geometry.cpp | 20 +- Examples/Python/src/ModuleEntry.cpp | 2 +- Examples/Python/src/Obj.cpp | 5 + Examples/Python/src/Output.cpp | 8 +- .../Visualization/Visualization3DTests.cpp | 2 + 21 files changed, 366 insertions(+), 240 deletions(-) delete mode 100644 Core/include/Acts/Visualization/detail/ObjVisualization3D.ipp create mode 100644 Core/src/Visualization/ObjVisualization3D.cpp diff --git a/Core/include/Acts/Geometry/TrackingGeometry.hpp b/Core/include/Acts/Geometry/TrackingGeometry.hpp index b0ac658eecd..e8d48c5ae5e 100644 --- a/Core/include/Acts/Geometry/TrackingGeometry.hpp +++ b/Core/include/Acts/Geometry/TrackingGeometry.hpp @@ -145,6 +145,17 @@ class TrackingGeometry { const std::unordered_map& geoIdSurfaceMap() const; + /// Visualize a tracking geometry including substructure + /// @param helper The visualization helper that implement the output + /// @param gctx The geometry context + /// @param viewConfig Global view config + /// @param portalViewConfig View config for portals + /// @param sensitiveViewConfig View configuration for sensitive surfaces + void visualize(IVisualization3D& helper, const GeometryContext& gctx, + const ViewConfig& viewConfig = s_viewVolume, + const ViewConfig& portalViewConfig = s_viewPortal, + const ViewConfig& sensitiveViewConfig = s_viewSensitive) const; + private: // the known world std::shared_ptr m_world; diff --git a/Core/include/Acts/Geometry/TrackingVolume.hpp b/Core/include/Acts/Geometry/TrackingVolume.hpp index 1d1a5b6dc3c..c980314fa6d 100644 --- a/Core/include/Acts/Geometry/TrackingVolume.hpp +++ b/Core/include/Acts/Geometry/TrackingVolume.hpp @@ -26,6 +26,7 @@ #include "Acts/Utilities/BinnedArray.hpp" #include "Acts/Utilities/Logger.hpp" #include "Acts/Utilities/TransformRange.hpp" +#include "Acts/Visualization/ViewConfig.hpp" #include #include @@ -486,6 +487,17 @@ class TrackingVolume : public Volume { /// - positiveFaceXY GlueVolumesDescriptor& glueVolumesDescriptor(); + /// Produces a 3D visualization of this tracking volume + /// @param helper The visualization helper describing the output format + /// @param gctx The geometry context + /// @param viewConfig The view configuration + /// @param portalViewConfig View configuration for portals + /// @param sensitiveViewConfig View configuration for sensitive surfaces + void visualize(IVisualization3D& helper, const GeometryContext& gctx, + const ViewConfig& viewConfig, + const ViewConfig& portalViewConfig, + const ViewConfig& sensitiveViewConfig) const; + private: void connectDenseBoundarySurfaces( MutableTrackingVolumeVector& confinedDenseVolumes); diff --git a/Core/include/Acts/Geometry/Volume.hpp b/Core/include/Acts/Geometry/Volume.hpp index a59451efe09..9217e758444 100644 --- a/Core/include/Acts/Geometry/Volume.hpp +++ b/Core/include/Acts/Geometry/Volume.hpp @@ -122,7 +122,7 @@ class Volume : public GeometryObject { /// @param gctx The geometry context /// @param viewConfig The view configuration void visualize(IVisualization3D& helper, const GeometryContext& gctx, - const ViewConfig& viewConfig = {}) const; + const ViewConfig& viewConfig) const; protected: Transform3 m_transform; diff --git a/Core/include/Acts/Surfaces/Surface.hpp b/Core/include/Acts/Surfaces/Surface.hpp index 3a823671823..79153e8c98e 100644 --- a/Core/include/Acts/Surfaces/Surface.hpp +++ b/Core/include/Acts/Surfaces/Surface.hpp @@ -482,7 +482,7 @@ class Surface : public virtual GeometryObject, const GeometryContext& gctx, const Vector3& position) const = 0; void visualize(IVisualization3D& helper, const GeometryContext& gctx, - const ViewConfig& viewConfig = {}) const; + const ViewConfig& viewConfig = s_viewSurface) const; protected: /// Output Method for std::ostream, to be overloaded by child classes diff --git a/Core/include/Acts/Visualization/GeometryView3D.hpp b/Core/include/Acts/Visualization/GeometryView3D.hpp index 05053de31ef..fed07697a87 100644 --- a/Core/include/Acts/Visualization/GeometryView3D.hpp +++ b/Core/include/Acts/Visualization/GeometryView3D.hpp @@ -31,12 +31,6 @@ class DetectorVolume; class Portal; } // namespace Experimental -static const ViewConfig s_viewSensitive; -static const ViewConfig s_viewPassive; -static const ViewConfig s_viewVolume; -static const ViewConfig s_viewGrid; -static const ViewConfig s_viewLine; - struct GeometryView3D { /// Helper method to draw Polyhedron objects /// diff --git a/Core/include/Acts/Visualization/IVisualization3D.hpp b/Core/include/Acts/Visualization/IVisualization3D.hpp index 734ab3fe129..be8c5b96226 100644 --- a/Core/include/Acts/Visualization/IVisualization3D.hpp +++ b/Core/include/Acts/Visualization/IVisualization3D.hpp @@ -72,6 +72,12 @@ class IVisualization3D { /// Remove all contents of this helper /// virtual void clear() = 0; + + virtual ~IVisualization3D() = default; + + /// Start a new object context + /// @param name The name of the object + virtual void object(const std::string& name) = 0; }; /// Overload of the << operator to facilitate writing to streams. diff --git a/Core/include/Acts/Visualization/ObjVisualization3D.hpp b/Core/include/Acts/Visualization/ObjVisualization3D.hpp index 941e719f870..a9f496ce304 100644 --- a/Core/include/Acts/Visualization/ObjVisualization3D.hpp +++ b/Core/include/Acts/Visualization/ObjVisualization3D.hpp @@ -12,12 +12,8 @@ #include "Acts/Visualization/IVisualization3D.hpp" #include "Acts/Visualization/ViewConfig.hpp" -#include #include -#include -#include #include -#include #include #include @@ -26,14 +22,10 @@ namespace Acts { /// This helper produces output in the OBJ format. Note that colors are not /// supported in this implementation. /// -template class ObjVisualization3D : public IVisualization3D { public: - static_assert(std::is_same_v || std::is_same_v, - "Use either double or float"); - /// Stored value type, should be double or float - using ValueType = T; + using ValueType = double; /// Type of a vertex based on the value type using VertexType = Eigen::Matrix; @@ -77,22 +69,32 @@ class ObjVisualization3D : public IVisualization3D { /// @copydoc Acts::IVisualization3D::clear() void clear() final; + /// Start a new object context with a name + /// @param name The name of the object + void object(const std::string& name) final; + private: + struct Object { + std::string name; + std::vector vertices{}; + std::vector faces{}; + std::vector lines{}; + + /// The object data to be written + /// Map of colors to be written at given index position + std::map lineColors{}; + std::map vertexColors{}; + std::map faceColors{}; + }; + + Object& object(); + const Object& object() const; + /// The output parameters unsigned int m_outputPrecision = 4; double m_outputScalor = 1.; - /// The object data to be written - std::vector m_vertices; - std::vector m_faces; - std::vector m_lines; - /// Map of colors to be written at given index position - std::map m_lineColors; - std::map m_vertexColors; - std::map m_faceColors; -}; -#ifndef DOXYGEN -#include "detail/ObjVisualization3D.ipp" -#endif + std::vector m_objects; +}; } // namespace Acts diff --git a/Core/include/Acts/Visualization/PlyVisualization3D.hpp b/Core/include/Acts/Visualization/PlyVisualization3D.hpp index e2c94481b9c..a4367dc7e02 100644 --- a/Core/include/Acts/Visualization/PlyVisualization3D.hpp +++ b/Core/include/Acts/Visualization/PlyVisualization3D.hpp @@ -60,6 +60,10 @@ class PlyVisualization3D : public IVisualization3D { /// @copydoc Acts::IVisualization3D::clear() void clear() final; + void object(const std::string& /*name*/) final { + // Unimplemented + } + private: std::vector> m_vertices; std::vector m_faces; diff --git a/Core/include/Acts/Visualization/ViewConfig.hpp b/Core/include/Acts/Visualization/ViewConfig.hpp index 0414fa58aba..e2cf7b80618 100644 --- a/Core/include/Acts/Visualization/ViewConfig.hpp +++ b/Core/include/Acts/Visualization/ViewConfig.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include namespace Acts { @@ -49,17 +49,31 @@ struct Color { constexpr Color(double r, double g, double b) : Color{std::array{r, g, b}} {} + private: + constexpr static int hexToInt(std::string_view hex) { + constexpr auto hexCharToInt = [](char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else { + throw std::invalid_argument("Invalid hex character"); + } + }; + + int value = 0; + for (char c : hex) { + value = (value << 4) + hexCharToInt(c); + } + return value; + }; + + public: /// Constructor from hex string. The expected format is `#RRGGBB` /// @param hex The hex string constexpr explicit Color(std::string_view hex) { - auto hexToInt = [](std::string_view hexStr) { - int value = 0; - std::stringstream ss; - ss << std::hex << hexStr; - ss >> value; - return value; - }; - if (hex[0] == '#' && hex.size() == 7) { rgb[0] = hexToInt(hex.substr(1, 2)); // Extract R component rgb[1] = hexToInt(hex.substr(3, 2)); // Extract G component @@ -85,7 +99,6 @@ struct Color { /// @param rhs The second color /// @return True if the colors are equal friend bool operator==(const Color& lhs, const Color& rhs) = default; - /// Output stream operator /// @param os The output stream /// @param color The color to be printed @@ -99,6 +112,10 @@ struct Color { std::array rgb{}; }; +constexpr Color s_defaultSurfaceColor{"#0000aa"}; +constexpr Color s_defaultPortalColor{"#308c48"}; +constexpr Color s_defaultVolumColor{"#ffaa00"}; + /// @brief Struct to concentrate all visualization configurations /// in order to harmonize visualization interfaces struct ViewConfig { @@ -120,4 +137,12 @@ struct ViewConfig { std::filesystem::path outputName = std::filesystem::path(""); }; +static const ViewConfig s_viewSurface = {.color = {170, 170, 170}}; +static const ViewConfig s_viewPortal = {.color = Color{"#308c48"}}; +static const ViewConfig s_viewSensitive = {.color = {0, 180, 240}}; +static const ViewConfig s_viewPassive = {.color = {240, 280, 0}}; +static const ViewConfig s_viewVolume = {.color = {220, 220, 0}}; +static const ViewConfig s_viewGrid = {.color = {220, 0, 0}}; +static const ViewConfig s_viewLine = {.color = {0, 0, 220}}; + } // namespace Acts diff --git a/Core/include/Acts/Visualization/detail/ObjVisualization3D.ipp b/Core/include/Acts/Visualization/detail/ObjVisualization3D.ipp deleted file mode 100644 index 354ef518bef..00000000000 --- a/Core/include/Acts/Visualization/detail/ObjVisualization3D.ipp +++ /dev/null @@ -1,179 +0,0 @@ -// This file is part of the ACTS project. -// -// Copyright (C) 2016 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 https://mozilla.org/MPL/2.0/. - -template -void ObjVisualization3D::vertex(const Vector3& vtx, Color color) { - m_vertexColors[m_vertices.size()] = color; - m_vertices.push_back(vtx.template cast()); -} - -template -void ObjVisualization3D::line(const Vector3& a, const Vector3& b, - Color color) { - if (color != Color{0, 0, 0}) { - m_lineColors[m_lines.size()] = color; - } - // not implemented - vertex(a, color); - vertex(b, color); - m_lines.push_back({m_vertices.size() - 2, m_vertices.size() - 1}); -} - -template -void ObjVisualization3D::face(const std::vector& vtxs, - Color color) { - if (color != Color{0, 0, 0}) { - m_faceColors[m_faces.size()] = color; - } - FaceType idxs; - idxs.reserve(vtxs.size()); - for (const auto& vtx : vtxs) { - vertex(vtx, color); - idxs.push_back(m_vertices.size() - 1); - } - m_faces.push_back(std::move(idxs)); -} - -template -void ObjVisualization3D::faces(const std::vector& vtxs, - const std::vector& faces, - Color color) { - // No faces given - call the face() method - if (faces.empty()) { - face(vtxs, color); - } else { - if (color != Color{0, 0, 0}) { - m_faceColors[m_faces.size()] = color; - } - auto vtxoffs = m_vertices.size(); - if (color != Color{0, 0, 0}) { - m_vertexColors[m_vertices.size()] = color; - } - m_vertices.insert(m_vertices.end(), vtxs.begin(), vtxs.end()); - for (const auto& face : faces) { - if (face.size() == 2) { - m_lines.push_back({face[0] + vtxoffs, face[2] + vtxoffs}); - } else { - FaceType rawFace = face; - std::transform(rawFace.begin(), rawFace.end(), rawFace.begin(), - [&](std::size_t& iv) { return (iv + vtxoffs); }); - m_faces.push_back(rawFace); - } - } - } -} - -template -void ObjVisualization3D::write(const std::filesystem::path& path) const { - std::ofstream os; - std::filesystem::path objectpath = path; - if (!objectpath.has_extension()) { - objectpath.replace_extension(std::filesystem::path("obj")); - } - os.open(std::filesystem::absolute(objectpath).string()); - std::filesystem::path mtlpath = objectpath; - mtlpath.replace_extension(std::filesystem::path("mtl")); - - const std::string mtlpathString = std::filesystem::absolute(mtlpath).string(); - os << "mtllib " << mtlpathString << "\n"; - std::ofstream mtlos; - mtlos.open(mtlpathString); - - write(os, mtlos); - os.close(); - mtlos.close(); -} - -template -void ObjVisualization3D::write(std::ostream& os) const { - std::stringstream sterile; - write(os, sterile); -} - -template -void ObjVisualization3D::write(std::ostream& os, std::ostream& mos) const { - std::map materials; - - auto mixColor = [&](const Color& color) -> std::string { - std::string materialName; - materialName = "material_"; - materialName += std::to_string(color[0]) + std::string("_"); - materialName += std::to_string(color[1]) + std::string("_"); - materialName += std::to_string(color[2]); - - if (!materials.contains(materialName)) { - mos << "newmtl " << materialName << "\n"; - std::vector shadings = {"Ka", "Kd", "Ks"}; - for (const auto& shd : shadings) { - mos << shd << " " << std::to_string(color[0] / 256.) << " "; - mos << std::to_string(color[1] / 256.) << " "; - mos << std::to_string(color[2] / 256.) << " " - << "\n"; - } - mos << "\n"; - } - return std::string("usemtl ") + materialName; - }; - - std::size_t iv = 0; - Color lastVertexColor = {0, 0, 0}; - for (const VertexType& vtx : m_vertices) { - if (m_vertexColors.contains(iv)) { - auto color = m_vertexColors.find(iv)->second; - if (color != lastVertexColor) { - os << mixColor(color) << "\n"; - lastVertexColor = color; - } - } - - os << "v " << std::setprecision(m_outputPrecision) - << m_outputScalor * vtx.x() << " " << m_outputScalor * vtx.y() << " " - << m_outputScalor * vtx.z() << "\n"; - ++iv; - } - std::size_t il = 0; - Color lastLineColor = {0, 0, 0}; - for (const LineType& ln : m_lines) { - if (m_lineColors.contains(il)) { - auto color = m_lineColors.find(il)->second; - if (color != lastLineColor) { - os << mixColor(color) << "\n"; - lastLineColor = color; - } - } - os << "l " << ln.first + 1 << " " << ln.second + 1 << "\n"; - ++il; - } - std::size_t is = 0; - Color lastFaceColor = {0, 0, 0}; - for (const FaceType& fc : m_faces) { - if (m_faceColors.contains(is)) { - auto color = m_faceColors.find(is)->second; - if (color != lastFaceColor) { - os << mixColor(color) << "\n"; - lastFaceColor = color; - } - } - os << "f"; - for (auto fi : fc) { - os << " " << fi + 1; - } - os << "\n"; - ++is; - } -} - -template -void ObjVisualization3D::clear() { - m_vertices.clear(); - m_faces.clear(); - m_lines.clear(); - m_lineColors.clear(); - m_vertexColors.clear(); - m_faceColors.clear(); -} diff --git a/Core/src/Geometry/TrackingGeometry.cpp b/Core/src/Geometry/TrackingGeometry.cpp index b68d788d08b..f9207afbd18 100644 --- a/Core/src/Geometry/TrackingGeometry.cpp +++ b/Core/src/Geometry/TrackingGeometry.cpp @@ -85,3 +85,11 @@ const std::unordered_map& Acts::TrackingGeometry::geoIdSurfaceMap() const { return m_surfacesById; } + +void Acts::TrackingGeometry::visualize( + IVisualization3D& helper, const GeometryContext& gctx, + const ViewConfig& viewConfig, const ViewConfig& portalViewConfig, + const ViewConfig& sensitiveViewConfig) const { + highestTrackingVolume()->visualize(helper, gctx, viewConfig, portalViewConfig, + sensitiveViewConfig); +} diff --git a/Core/src/Geometry/TrackingVolume.cpp b/Core/src/Geometry/TrackingVolume.cpp index 9bcf9cc969f..ad2c939fcbd 100644 --- a/Core/src/Geometry/TrackingVolume.cpp +++ b/Core/src/Geometry/TrackingVolume.cpp @@ -723,4 +723,25 @@ void TrackingVolume::addSurface(std::shared_ptr surface) { m_surfaces.push_back(std::move(surface)); } +void TrackingVolume::visualize(IVisualization3D& helper, + const GeometryContext& gctx, + const ViewConfig& viewConfig, + const ViewConfig& portalViewConfig, + const ViewConfig& sensitiveViewConfig) const { + helper.object(volumeName()); + Volume::visualize(helper, gctx, viewConfig); + + if (!surfaces().empty()) { + helper.object(volumeName() + "_sensitives"); + } + for (const auto& surface : surfaces()) { + surface.visualize(helper, gctx, sensitiveViewConfig); + } + + for (const auto& child : volumes()) { + child.visualize(helper, gctx, viewConfig, portalViewConfig, + sensitiveViewConfig); + } +} + } // namespace Acts diff --git a/Core/src/Visualization/CMakeLists.txt b/Core/src/Visualization/CMakeLists.txt index 5d4f916af1c..567b1e1e236 100644 --- a/Core/src/Visualization/CMakeLists.txt +++ b/Core/src/Visualization/CMakeLists.txt @@ -1 +1,4 @@ -target_sources(ActsCore PRIVATE GeometryView3D.cpp EventDataView3D.cpp) +target_sources( + ActsCore + PRIVATE GeometryView3D.cpp EventDataView3D.cpp ObjVisualization3D.cpp +) diff --git a/Core/src/Visualization/GeometryView3D.cpp b/Core/src/Visualization/GeometryView3D.cpp index 17b7b5c7df9..f16d9e809cc 100644 --- a/Core/src/Visualization/GeometryView3D.cpp +++ b/Core/src/Visualization/GeometryView3D.cpp @@ -26,7 +26,6 @@ #include "Acts/Surfaces/RadialBounds.hpp" #include "Acts/Surfaces/Surface.hpp" #include "Acts/Surfaces/SurfaceArray.hpp" -#include "Acts/Utilities/BinnedArray.hpp" #include "Acts/Utilities/BinningType.hpp" #include "Acts/Utilities/IAxis.hpp" #include "Acts/Utilities/UnitVectors.hpp" @@ -36,18 +35,9 @@ #include #include #include -#include #include #include -namespace Acts::Experimental { -ViewConfig s_viewSensitive = {.color = {0, 180, 240}}; -ViewConfig s_viewPassive = {.color = {240, 280, 0}}; -ViewConfig s_viewVolume = {.color = {220, 220, 0}}; -ViewConfig s_viewGrid = {.color = {220, 0, 0}}; -ViewConfig s_viewLine = {.color = {0, 0, 220}}; -} // namespace Acts::Experimental - void Acts::GeometryView3D::drawPolyhedron(IVisualization3D& helper, const Polyhedron& polyhedron, const ViewConfig& viewConfig) { diff --git a/Core/src/Visualization/ObjVisualization3D.cpp b/Core/src/Visualization/ObjVisualization3D.cpp new file mode 100644 index 00000000000..777e883b3d3 --- /dev/null +++ b/Core/src/Visualization/ObjVisualization3D.cpp @@ -0,0 +1,211 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 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 https://mozilla.org/MPL/2.0/. + +#include "Acts/Visualization/ObjVisualization3D.hpp" + +#include +#include +#include + +namespace Acts { + +void ObjVisualization3D::vertex(const Vector3& vtx, Color color) { + auto& o = object(); + o.vertexColors[o.vertices.size()] = color; + o.vertices.push_back(vtx.template cast()); +} + +void ObjVisualization3D::line(const Vector3& a, const Vector3& b, Color color) { + auto& o = object(); + if (color != Color{0, 0, 0}) { + o.lineColors[o.lines.size()] = color; + } + // not implemented + vertex(a, color); + vertex(b, color); + o.lines.push_back({o.vertices.size() - 2, o.vertices.size() - 1}); +} + +void ObjVisualization3D::face(const std::vector& vtxs, Color color) { + auto& o = object(); + if (color != Color{0, 0, 0}) { + o.faceColors[o.faces.size()] = color; + } + FaceType idxs; + idxs.reserve(vtxs.size()); + for (const auto& vtx : vtxs) { + vertex(vtx, color); + idxs.push_back(o.vertices.size() - 1); + } + o.faces.push_back(std::move(idxs)); +} + +void ObjVisualization3D::faces(const std::vector& vtxs, + const std::vector& faces, + Color color) { + auto& o = object(); + // No faces given - call the face() method + if (faces.empty()) { + face(vtxs, color); + } else { + if (color != Color{0, 0, 0}) { + o.faceColors[o.faces.size()] = color; + } + auto vtxoffs = o.vertices.size(); + if (color != Color{0, 0, 0}) { + o.vertexColors[o.vertices.size()] = color; + } + o.vertices.insert(o.vertices.end(), vtxs.begin(), vtxs.end()); + for (const auto& face : faces) { + if (face.size() == 2) { + o.lines.push_back({face[0] + vtxoffs, face[2] + vtxoffs}); + } else { + FaceType rawFace; + std::ranges::transform( + face, std::back_inserter(rawFace), + [&](unsigned long iv) { return (iv + vtxoffs); }); + o.faces.push_back(rawFace); + } + } + } +} + +void ObjVisualization3D::write(const std::filesystem::path& path) const { + std::ofstream os; + std::filesystem::path objectpath = path; + if (!objectpath.has_extension()) { + objectpath.replace_extension(std::filesystem::path("obj")); + } + os.open(std::filesystem::absolute(objectpath).string()); + std::filesystem::path mtlpath = objectpath; + mtlpath.replace_extension(std::filesystem::path("mtl")); + + const std::string mtlpathString = std::filesystem::absolute(mtlpath).string(); + os << "mtllib " << mtlpathString << "\n"; + std::ofstream mtlos; + mtlos.open(mtlpathString); + + write(os, mtlos); + os.close(); + mtlos.close(); +} + +void ObjVisualization3D::write(std::ostream& os) const { + std::stringstream sterile; + write(os, sterile); +} + +void ObjVisualization3D::write(std::ostream& os, std::ostream& mos) const { + std::map> materials; + + auto mixColor = [&](const Color& color) { + std::string materialName; + materialName = "material_"; + materialName += std::to_string(color[0]) + std::string("_"); + materialName += std::to_string(color[1]) + std::string("_"); + materialName += std::to_string(color[2]); + + if (!materials.contains(materialName)) { + mos << "newmtl " << materialName << "\n"; + std::vector shadings = {"Ka", "Kd", "Ks"}; + for (const auto& shd : shadings) { + mos << shd << " " << std::to_string(color[0] / 256.) << " "; + mos << std::to_string(color[1] / 256.) << " "; + mos << std::to_string(color[2] / 256.) << " " << "\n"; + } + mos << "\n"; + } + return std::string("usemtl ") + materialName; + }; + + std::size_t vertexOffset = 0; + for (const auto& o : m_objects) { + if (!o.name.empty()) { + os << "o " << o.name << "\n"; + } + + std::size_t iv = 0; + Color lastVertexColor = {0, 0, 0}; + for (const VertexType& vtx : o.vertices) { + if (o.vertexColors.contains(iv)) { + auto color = o.vertexColors.find(iv)->second; + if (color != lastVertexColor) { + os << mixColor(color) << "\n"; + lastVertexColor = color; + } + } + + os << "v " << std::setprecision(m_outputPrecision) + << m_outputScalor * vtx.x() << " " << m_outputScalor * vtx.y() << " " + << m_outputScalor * vtx.z() << "\n"; + ++iv; + } + std::size_t il = 0; + Color lastLineColor = {0, 0, 0}; + for (const auto& [start, end] : o.lines) { + if (o.lineColors.contains(il)) { + auto color = o.lineColors.find(il)->second; + if (color != lastLineColor) { + os << mixColor(color) << "\n"; + lastLineColor = color; + } + } + os << "l " << vertexOffset + start + 1 << " " << vertexOffset + end + 1 + << "\n"; + ++il; + } + std::size_t is = 0; + Color lastFaceColor = {0, 0, 0}; + for (const FaceType& fc : o.faces) { + if (o.faceColors.contains(is)) { + auto color = o.faceColors.find(is)->second; + if (color != lastFaceColor) { + os << mixColor(color) << "\n"; + lastFaceColor = color; + } + } + os << "f"; + for (std::size_t fi : fc) { + os << " " << vertexOffset + fi + 1; + } + os << "\n"; + ++is; + } + + vertexOffset += iv; + } +} + +void ObjVisualization3D::clear() { + m_objects.clear(); +} + +void ObjVisualization3D::object(const std::string& name) { + if (name.empty()) { + throw std::invalid_argument{"Object name can not be empty"}; + } + m_objects.push_back(Object{.name = name}); +} + +ObjVisualization3D::Object& ObjVisualization3D::object() { + if (m_objects.empty()) { + m_objects.push_back(Object{.name = ""}); + } + + return m_objects.back(); +} + +const ObjVisualization3D::Object& ObjVisualization3D::object() const { + if (m_objects.empty()) { + throw std::runtime_error{"No objects present"}; + } + + return m_objects.back(); +} + +} // namespace Acts diff --git a/Examples/Python/python/acts/examples/odd.py b/Examples/Python/python/acts/examples/odd.py index 4e1efb1c783..a28058af78b 100644 --- a/Examples/Python/python/acts/examples/odd.py +++ b/Examples/Python/python/acts/examples/odd.py @@ -76,8 +76,9 @@ def getOpenDataDetector( } def geoid_hook(geoid, surface): + gctx = acts.GeometryContext() if geoid.volume() in volumeRadiusCutsMap: - r = math.sqrt(surface.center()[0] ** 2 + surface.center()[1] ** 2) + r = math.sqrt(surface.center(gctx)[0] ** 2 + surface.center(gctx)[1] ** 2) geoid.setExtra(1) for cut in volumeRadiusCutsMap[geoid.volume()]: diff --git a/Examples/Python/src/Geometry.cpp b/Examples/Python/src/Geometry.cpp index 51188ee93e4..05484845465 100644 --- a/Examples/Python/src/Geometry.cpp +++ b/Examples/Python/src/Geometry.cpp @@ -37,6 +37,7 @@ #include "Acts/Surfaces/SurfaceArray.hpp" #include "Acts/Utilities/Helpers.hpp" #include "Acts/Utilities/RangeXD.hpp" +#include "Acts/Visualization/ViewConfig.hpp" #include "ActsExamples/Geometry/VolumeAssociationTest.hpp" #include @@ -109,13 +110,12 @@ void addGeometry(Context& ctx) { { py::class_>(m, "Surface") + // Can't bind directly because GeometryObject is virtual base of Surface .def("geometryId", - [](Acts::Surface& self) { return self.geometryId(); }) - .def("center", - [](Acts::Surface& self) { - return self.center(Acts::GeometryContext{}); - }) - .def("type", [](Acts::Surface& self) { return self.type(); }); + [](const Surface& self) { return self.geometryId(); }) + .def("center", &Surface::center) + .def("type", &Surface::type) + .def("visualize", &Surface::visualize); } { @@ -167,7 +167,11 @@ void addGeometry(Context& ctx) { }) .def_property_readonly( "highestTrackingVolume", - &Acts::TrackingGeometry::highestTrackingVolumePtr); + &Acts::TrackingGeometry::highestTrackingVolumePtr) + .def("visualize", &Acts::TrackingGeometry::visualize, py::arg("helper"), + py::arg("gctx"), py::arg("viewConfig") = s_viewVolume, + py::arg("portalViewConfig") = s_viewPortal, + py::arg("sensitiveViewConfig") = s_viewSensitive); } { @@ -287,7 +291,7 @@ void addExperimentalGeometry(Context& ctx) { for (const auto& surface : smap) { auto gid = surface->geometryId(); // Exclusion criteria - if (sensitiveOnly and gid.sensitive() == 0) { + if (sensitiveOnly && gid.sensitive() == 0) { continue; }; surfaceVolumeLayerMap[gid.volume()][gid.layer()].push_back(surface); diff --git a/Examples/Python/src/ModuleEntry.cpp b/Examples/Python/src/ModuleEntry.cpp index 77348edaf1c..1a0e27db907 100644 --- a/Examples/Python/src/ModuleEntry.cpp +++ b/Examples/Python/src/ModuleEntry.cpp @@ -120,6 +120,7 @@ PYBIND11_MODULE(ActsPythonBindings, m) { addAlgebra(ctx); addBinning(ctx); addEventData(ctx); + addOutput(ctx); addPropagation(ctx); addGeometryBuildingGen1(ctx); @@ -128,7 +129,6 @@ PYBIND11_MODULE(ActsPythonBindings, m) { addMagneticField(ctx); addMaterial(ctx); - addOutput(ctx); addDetector(ctx); addExampleAlgorithms(ctx); addInput(ctx); diff --git a/Examples/Python/src/Obj.cpp b/Examples/Python/src/Obj.cpp index 5ca2df40033..09acb0c53b6 100644 --- a/Examples/Python/src/Obj.cpp +++ b/Examples/Python/src/Obj.cpp @@ -6,6 +6,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +#include "Acts/Visualization/IVisualization3D.hpp" #include #include #include @@ -19,6 +20,7 @@ #include #include +#include namespace py = pybind11; using namespace pybind11::literals; @@ -87,5 +89,8 @@ void addObj(Context& ctx) { obj.write(fileName); }); } + + py::class_(m, "ObjVisualization3D") + .def(py::init<>()); } } // namespace Acts::Python diff --git a/Examples/Python/src/Output.cpp b/Examples/Python/src/Output.cpp index b53ba905700..11472d8e203 100644 --- a/Examples/Python/src/Output.cpp +++ b/Examples/Python/src/Output.cpp @@ -10,6 +10,7 @@ #include "Acts/Geometry/GeometryHierarchyMap.hpp" #include "Acts/Plugins/Python/Utilities.hpp" #include "Acts/Utilities/Logger.hpp" +#include "Acts/Visualization/IVisualization3D.hpp" #include "Acts/Visualization/ViewConfig.hpp" #include "ActsExamples/Digitization/DigitizationConfig.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" @@ -57,6 +58,7 @@ #include #include +#include namespace Acts { class TrackingGeometry; @@ -130,10 +132,14 @@ void addOutput(Context& ctx) { .def(py::init<>()) .def(py::init()) .def(py::init()) - .def(py::init()) + .def(py::init()) .def_readonly("rgb", &Color::rgb); } + py::class_(m, "IVisualization3D") + .def("write", py::overload_cast( + &IVisualization3D::write, py::const_)); + { using Writer = ActsExamples::ObjTrackingGeometryWriter; auto w = py::class_>( diff --git a/Tests/UnitTests/Core/Visualization/Visualization3DTests.cpp b/Tests/UnitTests/Core/Visualization/Visualization3DTests.cpp index 58958bbbe6c..ea3e810d119 100644 --- a/Tests/UnitTests/Core/Visualization/Visualization3DTests.cpp +++ b/Tests/UnitTests/Core/Visualization/Visualization3DTests.cpp @@ -335,6 +335,8 @@ BOOST_AUTO_TEST_CASE(ColorTests) { BOOST_CHECK_EQUAL(grey, Color(std::array{128 / 255.0, 128 / 255.0, 128 / 255.0})); BOOST_CHECK_EQUAL(grey, Color(128 / 255.0, 128 / 255.0, 128 / 255.0)); + + static_assert(Color{"#0000ff"} == Color(0, 0, 255)); } BOOST_AUTO_TEST_SUITE_END()