GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 91.0% 142 / 0 / 156
Functions: 100.0% 27 / 0 / 27
Branches: 46.9% 120 / 0 / 256

src/G4OCCTSolid.cc
Line Branch Exec Source
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // Copyright (C) 2026 G4OCCT Contributors
3
4 /// @file G4OCCTSolid.cc
5 /// @brief Geant4 adapter over the shared G4OCCT solid-query kernel.
6
7 #include "G4OCCT/G4OCCTSolid.hh"
8
9 #include "G4OCCTSolidKernel.hh"
10
11 #include <BRepMesh_IncrementalMesh.hxx>
12 #include <BRep_Tool.hxx>
13 #include <IFSelect_ReturnStatus.hxx>
14 #include <Poly_Triangulation.hxx>
15 #include <STEPControl_Reader.hxx>
16 #include <TopAbs_Orientation.hxx>
17 #include <TopAbs_ShapeEnum.hxx>
18 #include <TopExp_Explorer.hxx>
19 #include <TopLoc_Location.hxx>
20 #include <TopoDS.hxx>
21 #include <TopoDS_Face.hxx>
22 #include <gp_Pnt.hxx>
23
24 #include <G4AffineTransform.hh>
25 #include <G4BoundingEnvelope.hh>
26 #include <G4Exception.hh>
27 #include <G4Polyhedron.hh>
28 #include <G4TessellatedSolid.hh>
29 #include <G4ThreadLocalSingleton.hh>
30 #include <G4TriangularFacet.hh>
31 #include <G4VGraphicsScene.hh>
32 #include <G4VisExtent.hh>
33
34 #include <condition_variable>
35 #include <cstdint>
36 #include <limits>
37 #include <memory>
38 #include <mutex>
39 #include <stdexcept>
40 #include <utility>
41
42 using g4occt::detail::G4OCCTSolidKernel;
43
44 class G4OCCTSolid::Impl {
45 public:
46
3/6
✓ Branch 0 taken 245 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 245 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 245 times.
✗ Branch 5 not taken.
245 explicit Impl(const TopoDS_Shape& shape) : kernel(shape) {}
47
48 G4OCCTSolidKernel kernel;
49 mutable G4ThreadLocalSingleton<G4OCCTSolidKernel::ClassifierCache> classifierCache;
50 mutable G4ThreadLocalSingleton<G4OCCTSolidKernel::IntersectorCache> intersectorCache;
51 mutable G4ThreadLocalSingleton<G4OCCTSolidKernel::SphereCacheData> sphereCache;
52
53 mutable std::unique_ptr<G4Polyhedron> cachedPolyhedron;
54 245 mutable std::uint64_t polyhedronGeneration{std::numeric_limits<std::uint64_t>::max()};
55 245 mutable bool polyhedronBuilding{false};
56 mutable std::mutex polyhedronMutex;
57 mutable std::condition_variable polyhedronCV;
58 };
59
60 namespace {
61
62 5063955 EInside ToG4Inside(const G4OCCTSolidKernel::PointClassification classification) {
63
3/4
✓ Branch 0 taken 234 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1876138 times.
✓ Branch 3 taken 3187583 times.
5063955 switch (classification) {
64 case G4OCCTSolidKernel::PointClassification::kInside:
65 1876138 return kInside;
66 case G4OCCTSolidKernel::PointClassification::kSurface:
67 234 return kSurface;
68 3187583 case G4OCCTSolidKernel::PointClassification::kOutside:
69 default:
70 3187583 return kOutside;
71 }
72 5063955 }
73
74 } // namespace
75
76 247 G4OCCTSolid::G4OCCTSolid(const G4String& name, const TopoDS_Shape& shape)
77
2/2
✓ Branch 0 taken 245 times.
✓ Branch 1 taken 2 times.
247 : G4VSolid(name), fImpl(std::make_unique<Impl>(shape)) {}
78
79 392 G4OCCTSolid::~G4OCCTSolid() = default;
80
81 9 G4OCCTSolid* G4OCCTSolid::FromSTEP(const G4String& name, const std::string& path) {
82 9 STEPControl_Reader reader;
83
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 const IFSelect_ReturnStatus status = reader.ReadFile(path.c_str());
84
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
9 if (status != IFSelect_RetDone) {
85
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
1 throw std::runtime_error("G4OCCTSolid::FromSTEP: failed to read STEP file \"" + path + "\"");
86 }
87
88
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 const Standard_Integer nRoots = reader.NbRootsForTransfer();
89
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
8 if (nRoots == 0) {
90
4/8
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
1 throw std::runtime_error("G4OCCTSolid::FromSTEP: STEP file \"" + path +
91 "\" contains no root "
92 "shapes");
93 }
94
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 if (reader.TransferRoots() <= 0) {
95 ✗ throw std::runtime_error("G4OCCTSolid::FromSTEP: failed to transfer STEP roots from \"" + path +
96 "\"");
97 }
98
99
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 const TopoDS_Shape shape = reader.OneShape();
100
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 if (shape.IsNull()) {
101 ✗ throw std::runtime_error("G4OCCTSolid::FromSTEP: STEP file \"" + path +
102 "\" yielded a null shape");
103 }
104
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 return new G4OCCTSolid(name, shape);
105 11 }
106
107 5063955 EInside G4OCCTSolid::Inside(const G4ThreeVector& p) const {
108 10127910 return ToG4Inside(fImpl->kernel.ClassifyPoint(p, *fImpl->classifierCache.Instance(),
109 5063955 *fImpl->intersectorCache.Instance(),
110 5063955 *fImpl->sphereCache.Instance()));
111 }
112
113 465851 G4ThreeVector G4OCCTSolid::SurfaceNormal(const G4ThreeVector& p) const {
114 465851 return fImpl->kernel.SurfaceNormal(p);
115 }
116
117 2679557 G4double G4OCCTSolid::DistanceToIn(const G4ThreeVector& p, const G4ThreeVector& v) const {
118 2679557 return fImpl->kernel.DistanceToIn(p, v, *fImpl->intersectorCache.Instance());
119 }
120
121 2502254 G4double G4OCCTSolid::DistanceToIn(const G4ThreeVector& p) const {
122 2502254 return fImpl->kernel.DistanceToIn(p, *fImpl->classifierCache.Instance());
123 }
124
125 59407 G4double G4OCCTSolid::DistanceToOut(const G4ThreeVector& p, const G4ThreeVector& v,
126 const G4bool calcNorm, G4bool* validNorm,
127 G4ThreeVector* n) const {
128 118814 return fImpl->kernel.DistanceToOut(p, v, *fImpl->intersectorCache.Instance(), calcNorm, validNorm,
129 59407 n);
130 }
131
132 40174 G4double G4OCCTSolid::DistanceToOut(const G4ThreeVector& p) const {
133 40174 return fImpl->kernel.DistanceToOut(p, *fImpl->sphereCache.Instance());
134 }
135
136 200 G4ThreeVector G4OCCTSolid::GetPointOnSurface() const {
137
1/2
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
200 return fImpl->kernel.GetPointOnSurface(GetName().c_str());
138 ✗ }
139
140 25447 G4double G4OCCTSolid::ExactDistanceToIn(const G4ThreeVector& p) const {
141 25447 return fImpl->kernel.ExactDistanceToIn(p, *fImpl->classifierCache.Instance());
142 }
143
144 40117 G4double G4OCCTSolid::ExactDistanceToOut(const G4ThreeVector& p) const {
145 40117 return fImpl->kernel.ExactDistanceToOut(p);
146 }
147
148 5 G4double G4OCCTSolid::GetCubicVolume() { return fImpl->kernel.GetCubicVolume(); }
149
150 3 G4double G4OCCTSolid::GetSurfaceArea() { return fImpl->kernel.GetSurfaceArea(); }
151
152
1/2
✓ Branch 0 taken 58 times.
✗ Branch 1 not taken.
58 G4GeometryType G4OCCTSolid::GetEntityType() const { return "G4OCCTSolid"; }
153
154 165 G4VisExtent G4OCCTSolid::GetExtent() const {
155 165 const auto& bounds = fImpl->kernel.Bounds();
156 330 return {bounds.min.x(), bounds.max.x(), bounds.min.y(),
157 165 bounds.max.y(), bounds.min.z(), bounds.max.z()};
158 }
159
160 81 void G4OCCTSolid::BoundingLimits(G4ThreeVector& pMin, G4ThreeVector& pMax) const {
161 81 const auto& bounds = fImpl->kernel.Bounds();
162 81 pMin = bounds.min;
163 81 pMax = bounds.max;
164 81 }
165
166 5 G4bool G4OCCTSolid::CalculateExtent(const EAxis pAxis, const G4VoxelLimits& pVoxelLimit,
167 const G4AffineTransform& pTransform, G4double& pMin,
168 G4double& pMax) const {
169 5 const auto& bounds = fImpl->kernel.Bounds();
170 5 const G4BoundingEnvelope envelope(bounds.min, bounds.max);
171
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return envelope.CalculateExtent(pAxis, pVoxelLimit, G4Transform3D(pTransform), pMin, pMax);
172 5 }
173
174 162 void G4OCCTSolid::DescribeYourselfTo(G4VGraphicsScene& scene) const { scene.AddSolid(*this); }
175
176 3 G4Polyhedron* G4OCCTSolid::CreatePolyhedron() const {
177 3 const auto currentGeneration = fImpl->kernel.ShapeGeneration();
178
179 {
180 3 std::unique_lock<std::mutex> lock(fImpl->polyhedronMutex);
181
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
6 fImpl->polyhedronCV.wait(lock, [this, currentGeneration] {
182
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 return !fImpl->polyhedronBuilding || fImpl->polyhedronGeneration == currentGeneration;
183 });
184
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
3 if (fImpl->cachedPolyhedron && fImpl->polyhedronGeneration == currentGeneration) {
185
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 return new G4Polyhedron(*fImpl->cachedPolyhedron);
186 }
187 2 fImpl->polyhedronBuilding = true;
188 3 }
189
190 2 std::unique_ptr<G4Polyhedron> freshPolyhedron;
191 try {
192
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 BRepMesh_IncrementalMesh mesher(fImpl->kernel.Shape(), g4occt::detail::kOCCTRelativeDeflection,
193 /*isRelative=*/Standard_True);
194 (void)mesher;
195
196
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 G4TessellatedSolid tessellatedSolid(GetName() + "_polyhedron");
197 2 G4int facetCount = 0;
198
199
5/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 2 times.
14 for (TopExp_Explorer explorer(fImpl->kernel.Shape(), TopAbs_FACE); explorer.More();
200
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 explorer.Next()) {
201
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 const TopoDS_Face& face = TopoDS::Face(explorer.Current());
202
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 TopLoc_Location location;
203
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 const Handle(Poly_Triangulation) & triangulation = BRep_Tool::Triangulation(face, location);
204
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 if (triangulation.IsNull()) {
205 ✗ continue;
206 }
207
208
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 const gp_Trsf& transform = location.Transformation();
209
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 const bool reverseWinding = face.Orientation() == TopAbs_REVERSED;
210
211
4/6
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✓ Branch 5 taken 12 times.
36 for (Standard_Integer triangleIndex = 1; triangleIndex <= triangulation->NbTriangles();
212 24 ++triangleIndex) {
213 24 Standard_Integer index1 = 0;
214 24 Standard_Integer index2 = 0;
215 24 Standard_Integer index3 = 0;
216
3/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 triangulation->Triangle(triangleIndex).Get(index1, index2, index3);
217
218
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 12 times.
24 if (reverseWinding) {
219 12 std::swap(index2, index3);
220 12 }
221
222
3/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 const gp_Pnt point1 = triangulation->Node(index1).Transformed(transform);
223
3/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 const gp_Pnt point2 = triangulation->Node(index2).Transformed(transform);
224
3/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 const gp_Pnt point3 = triangulation->Node(index3).Transformed(transform);
225
226 24 auto* facet =
227
6/14
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 24 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 24 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
48 new G4TriangularFacet(G4ThreeVector(point1.X(), point1.Y(), point1.Z()),
228
4/8
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
24 G4ThreeVector(point2.X(), point2.Y(), point2.Z()),
229
4/8
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
24 G4ThreeVector(point3.X(), point3.Y(), point3.Z()), ABSOLUTE);
230
4/8
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 24 times.
24 if (!facet->IsDefined() || !tessellatedSolid.AddFacet(facet)) {
231 ✗ delete facet;
232 ✗ continue;
233 }
234 24 ++facetCount;
235 24 }
236 12 }
237
238
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (facetCount > 0) {
239
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 tessellatedSolid.SetSolidClosed(true);
240
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 G4Polyhedron* tmp = tessellatedSolid.GetPolyhedron();
241
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (tmp != nullptr) {
242
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 freshPolyhedron = std::make_unique<G4Polyhedron>(*tmp);
243 2 }
244 2 }
245 2 } catch (...) {
246 ✗ std::unique_lock<std::mutex> lock(fImpl->polyhedronMutex);
247 ✗ fImpl->polyhedronBuilding = false;
248 ✗ lock.unlock();
249 ✗ fImpl->polyhedronCV.notify_all();
250 ✗ throw;
251 ✗ }
252
253 {
254
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::unique_lock<std::mutex> lock(fImpl->polyhedronMutex);
255 2 bool cacheWritten = false;
256
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 if (freshPolyhedron && fImpl->kernel.ShapeGeneration() == currentGeneration) {
257
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 fImpl->cachedPolyhedron = std::make_unique<G4Polyhedron>(*freshPolyhedron);
258 2 fImpl->polyhedronGeneration = currentGeneration;
259 2 cacheWritten = true;
260 2 }
261 2 fImpl->polyhedronBuilding = false;
262 2 fImpl->polyhedronCV.notify_all();
263
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (cacheWritten) {
264
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 return new G4Polyhedron(*fImpl->cachedPolyhedron);
265 }
266 2 }
267
268 ✗ return freshPolyhedron ? new G4Polyhedron(*freshPolyhedron) : nullptr;
269 3 }
270
271 54 std::ostream& G4OCCTSolid::StreamInfo(std::ostream& os) const {
272 108 os << "-----------------------------------------------------------\n"
273
2/4
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
54 << " *** Dump for solid - " << GetName() << " ***\n"
274
1/2
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
54 << " ===================================================\n"
275
1/2
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
54 << " Solid type: G4OCCTSolid\n"
276
5/10
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 54 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 54 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 54 times.
✗ Branch 9 not taken.
54 << " OCCT shape type: " << fImpl->kernel.Shape().ShapeType() << "\n"
277
1/2
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
54 << "-----------------------------------------------------------\n";
278 54 return os;
279 ✗ }
280
281 10 const TopoDS_Shape& G4OCCTSolid::GetOCCTShape() const { return fImpl->kernel.Shape(); }
282
283 5 void G4OCCTSolid::SetOCCTShape(const TopoDS_Shape& shape) {
284 5 fImpl->kernel.SetShape(shape);
285 5 std::unique_lock<std::mutex> lock(fImpl->polyhedronMutex);
286 5 fImpl->cachedPolyhedron.reset();
287 5 fImpl->polyhedronGeneration = std::numeric_limits<std::uint64_t>::max();
288 5 }
289