GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 89.4% 161 / 0 / 180
Functions: 100.0% 11 / 0 / 11
Branches: 49.4% 153 / 0 / 310

src/G4OCCTAssemblyVolume.cc
Line Branch Exec Source
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // Copyright (C) 2026 G4OCCT Contributors
3
4 /// @file G4OCCTAssemblyVolume.cc
5 /// @brief Implementation of G4OCCTAssemblyVolume.
6
7 #include "G4OCCT/G4OCCTAssemblyVolume.hh"
8
9 #include "G4OCCT/G4OCCTSensitiveDetectorMap.hh"
10 #include "G4OCCT/G4OCCTSolid.hh"
11
12 // OCCT BRep / geometry
13 #include <BRepBndLib.hxx>
14 #include <Bnd_Box.hxx>
15 #include <TopLoc_Location.hxx>
16 #include <TopoDS_Shape.hxx>
17 #include <gp_Trsf.hxx>
18 #include <gp_Vec.hxx>
19
20 // OCCT XDE
21 #include <IFSelect_ReturnStatus.hxx>
22 #include <STEPCAFControl_Reader.hxx>
23 #include <TDataStd_Name.hxx>
24 #include <TDF_Label.hxx>
25 #include <TDF_LabelSequence.hxx>
26 #include <TDF_Tool.hxx>
27 #include <TDocStd_Application.hxx>
28 #include <TDocStd_Document.hxx>
29 #include <XCAFDoc_DocumentTool.hxx>
30 #include <XCAFDoc_Location.hxx>
31 #include <XCAFDoc_MaterialTool.hxx>
32 #include <XCAFDoc_ShapeTool.hxx>
33
34 // Geant4
35 #include <G4Exception.hh>
36 #include <G4RotationMatrix.hh>
37 #include <G4ThreeVector.hh>
38
39 // CLHEP
40 #include <CLHEP/Vector/RotationInterfaces.h>
41
42 #include <map>
43 #include <stdexcept>
44 #include <string>
45 #include <utility>
46
47 // ── Internal helpers ──────────────────────────────────────────────────────────
48
49 namespace {
50
51 /// Convert an OCCT @p trsf to a Geant4 rotation + translation pair.
52 /// STEP/OCCT and Geant4 both use millimetres; no unit conversion is needed.
53 24 std::pair<G4RotationMatrix, G4ThreeVector> TrsfToG4(const gp_Trsf& trsf) {
54 // CLHEP::HepRotation's 9-argument (row-major) constructor is protected.
55 // Use the public HepRep3x3 constructor instead.
56 48 G4RotationMatrix rot(CLHEP::HepRep3x3(trsf.Value(1, 1), trsf.Value(1, 2), trsf.Value(1, 3),
57 24 trsf.Value(2, 1), trsf.Value(2, 2), trsf.Value(2, 3),
58 24 trsf.Value(3, 1), trsf.Value(3, 2), trsf.Value(3, 3)));
59
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 trans(trsf.Value(1, 4), trsf.Value(2, 4), trsf.Value(3, 4));
60
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 return {rot, trans};
61 24 }
62
63 /// Compose a TopLoc_Location into a gp_Trsf by walking the datum-power chain.
64 ///
65 /// This follows the approach documented in docs/step_assembly_import.md §4.3.
66 /// For a simple (non-compound) location with power ±1 the loop runs exactly
67 /// once. Compound locations with power > 1 are correctly handled by repeated
68 /// application of the datum transformation.
69 4 gp_Trsf LocationToTrsf(const TopLoc_Location& loc) {
70 4 gp_Trsf result; // identity by default
71
5/8
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
8 for (TopLoc_Location cursor = loc; !cursor.IsIdentity(); cursor = cursor.NextLocation()) {
72
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 gp_Trsf datum = cursor.FirstDatum()->Trsf();
73
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 Standard_Integer power = cursor.FirstPower();
74
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (power < 0) {
75 ✗ datum.Invert();
76 ✗ power = -power;
77 ✗ }
78
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 gp_Trsf step; // identity
79
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 for (Standard_Integer k = 0; k < power; ++k) {
80
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 step.Multiply(datum);
81 4 }
82
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 result.Multiply(step);
83 4 }
84 4 return result;
85 ✗ }
86
87 /// Retrieve the XDE name attribute for @p label, or return an empty string.
88 44 G4String GetLabelName(const TDF_Label& label) {
89 44 Handle(TDataStd_Name) nameAttr;
90
3/6
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
44 if (label.FindAttribute(TDataStd_Name::GetID(), nameAttr)) {
91
3/6
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 44 times.
✗ Branch 5 not taken.
44 TCollection_AsciiString ascii(nameAttr->Get());
92
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
44 return G4String(ascii.ToCString());
93 44 }
94 ✗ return G4String{};
95 44 }
96
97 /// Read the material name attached to @p label via the XDE material tool.
98 /// Returns an empty string when no material attribute is found.
99 22 G4String GetMaterialName(const TDF_Label& label, const Handle(XCAFDoc_MaterialTool) & matTool) {
100 22 Handle(TCollection_HAsciiString) matName;
101
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 Handle(TCollection_HAsciiString) matDescription;
102 22 Standard_Real density = 0.0;
103
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 Handle(TCollection_HAsciiString) densityName;
104
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 Handle(TCollection_HAsciiString) densityValType;
105
3/6
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 22 times.
22 if (matTool->GetMaterial(label, matName, matDescription, density, densityName, densityValType)) {
106 ✗ if (!matName.IsNull()) {
107 ✗ return G4String(matName->ToCString());
108 }
109 ✗ }
110 22 return G4String{};
111 22 }
112
113 /// Recenter @p shape by moving its bounding-box centroid to the OCCT origin
114 /// using Strategy C from docs/reference_position.md (shape.Moved(), no BRep copy).
115 ///
116 /// On return @p centroid holds the location of the centroid in the original
117 /// shape frame (i.e. the opposite of the applied recentering translation).
118 /// Callers must compose this offset into the placement transformation:
119 /// T_eff = composedTrsf * Translate(centroid)
120 22 TopoDS_Shape RecenterShape(const TopoDS_Shape& shape, gp_Vec& centroid) {
121 22 Bnd_Box bbox;
122 22 BRepBndLib::AddOptimal(shape, bbox, Standard_False);
123
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (bbox.IsVoid()) {
124 ✗ centroid = gp_Vec(0.0, 0.0, 0.0);
125 ✗ return shape;
126 }
127 22 Standard_Real xMin = 0.0, yMin = 0.0, zMin = 0.0;
128 22 Standard_Real xMax = 0.0, yMax = 0.0, zMax = 0.0;
129 22 bbox.Get(xMin, yMin, zMin, xMax, yMax, zMax);
130
131 22 centroid = gp_Vec(0.5 * (xMin + xMax), 0.5 * (yMin + yMax), 0.5 * (zMin + zMax));
132
133 22 gp_Trsf centerTrsf;
134 22 centerTrsf.SetTranslation(gp_Vec(-centroid.X(), -centroid.Y(), -centroid.Z()));
135
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 return shape.Moved(TopLoc_Location(centerTrsf));
136 22 }
137
138 /// Make a name unique within @p usedNames by appending `_1`, `_2`, … as needed.
139 ///
140 /// On the first call with a given @p name the name is returned unchanged.
141 /// On subsequent calls a numeric suffix is appended to avoid duplicates.
142 22 G4String MakeUniqueName(const G4String& name, std::map<G4String, int>& usedNames) {
143 28 auto [it, inserted] = usedNames.emplace(name, 0);
144
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 5 times.
22 if (inserted) {
145 17 return name;
146 }
147 // Generate a unique candidate by incrementing the per-base counter.
148 5 G4String candidate;
149 5 do {
150 6 ++(it->second);
151
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 candidate = name + "_" + std::to_string(it->second);
152
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 time.
6 } while (usedNames.count(candidate) > 0);
153
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 usedNames.emplace(candidate, 0);
154 5 return candidate;
155 27 }
156
157 /// Return a stable string key for @p label suitable for use in a std::map.
158 ///
159 /// `TDF_Label` does not define `operator<`, so it cannot be used directly as a
160 /// std::map key. The colon-separated tag path (e.g. "0:1:1:2") is unique
161 /// within a document and provides a stable ordering.
162 24 std::string LabelKey(const TDF_Label& label) {
163 24 TCollection_AsciiString entry;
164
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 TDF_Tool::Entry(label, entry);
165
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 return std::string(entry.ToCString());
166 24 }
167
168 } // namespace
169
170 // ── BuildContext ──────────────────────────────────────────────────────────────
171
172 /// State threaded through the recursive XDE label traversal.
173 struct G4OCCTAssemblyVolume::BuildContext {
174 /// OCCT shape tool for the XDE document.
175 Handle(XCAFDoc_ShapeTool) shapeTool;
176 /// OCCT material tool for the XDE document.
177 Handle(XCAFDoc_MaterialTool) matTool;
178 /// User-supplied material map.
179 const G4OCCTMaterialMap& materialMap;
180
181 /// Prototype map: XDE label entry string → (logical volume, centroid in original shape frame).
182 /// The centroid is stored alongside the logical volume so that repeated instances can compose
183 /// the correct recentering offset without recomputing the bounding box.
184 std::map<std::string, std::pair<G4OCCTLogicalVolume*, gp_Vec>> prototypeMap;
185 /// Names already used by logical volumes; used to disambiguate duplicates.
186 std::map<G4String, int> usedNames;
187 /// Flat collection of all created logical volumes (output to caller).
188 std::map<G4String, G4OCCTLogicalVolume*>* logicalVolumes{nullptr};
189 };
190
191 // ── Recursive import ──────────────────────────────────────────────────────────
192
193 26 void G4OCCTAssemblyVolume::ImportLabel(const TDF_Label& label, G4AssemblyVolume* parentAssembly,
194 const gp_Trsf& composedTrsf, BuildContext& ctx) {
195 26 const Handle(XCAFDoc_ShapeTool) & shapeTool = ctx.shapeTool;
196
197 // ── Assembly label: recurse into components ─────────────────────────────────
198
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 2 times.
26 if (shapeTool->IsAssembly(label)) {
199 2 TDF_LabelSequence components;
200
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 shapeTool->GetComponents(label, components, /*recursive=*/Standard_False);
201
202
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 2 times.
6 for (Standard_Integer i = 1; i <= components.Length(); ++i) {
203
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 const TDF_Label& comp = components.Value(i); // always a reference label
204
205 // Extract the component's placement in the parent frame.
206 // FindAttribute returns false if no location is stored; in that case
207 // compLoc remains default-constructed (identity).
208
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 TopLoc_Location compLoc;
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 Handle(XCAFDoc_Location) locAttr;
210
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 if (comp.FindAttribute(XCAFDoc_Location::GetID(), locAttr)) {
211
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 compLoc = locAttr->Get();
212 4 }
213
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 gp_Trsf compTrsf = LocationToTrsf(compLoc);
214
215 // Compose: first apply component's local placement, then the parent's.
216 4 gp_Trsf childTrsf = composedTrsf;
217
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 childTrsf.Multiply(compTrsf);
218
219 // Resolve reference to the actual shape label, then recurse.
220
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 TDF_Label referred;
221
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 if (shapeTool->GetReferredShape(comp, referred)) {
222
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 ImportLabel(referred, parentAssembly, childTrsf, ctx);
223 4 } else {
224 ✗ G4Exception("G4OCCTAssemblyVolume::ImportLabel", "G4OCCT_Asm001", JustWarning,
225 "Assembly component has no referred shape; skipping.");
226 }
227 4 }
228 return;
229 2 }
230
231 // ── Simple shape label: create or reuse a logical volume ────────────────────
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (shapeTool->IsSimpleShape(label)) {
233 24 const std::string labelKey = LabelKey(label);
234
235 24 G4OCCTLogicalVolume* lv = nullptr;
236
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 auto protoIt = ctx.prototypeMap.find(labelKey);
237
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 gp_Vec centroid;
238
239
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
24 if (protoIt != ctx.prototypeMap.end()) {
240 // Reuse the existing logical volume — instance sharing.
241 // Retrieve the cached centroid to avoid recomputing the bounding box.
242 2 lv = protoIt->second.first;
243 2 centroid = protoIt->second.second;
244 2 } else {
245 // First encounter: build solid + logical volume.
246
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 TopoDS_Shape rawShape = shapeTool->GetShape(label);
247
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if (rawShape.IsNull()) {
248 ✗ G4Exception("G4OCCTAssemblyVolume::ImportLabel", "G4OCCT_Asm002", JustWarning,
249 "Simple-shape label has a null OCCT shape; skipping.");
250 ✗ return;
251 }
252
253 // Resolve material by XDE material attribute, falling back to the part
254 // (label) name when no material attribute is present in the STEP file.
255 // This accommodates STEP writers that do not write material attributes.
256
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 G4String matKey = GetMaterialName(label, ctx.matTool);
257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (matKey.empty()) {
258
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 matKey = GetLabelName(label);
259 22 }
260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (matKey.empty()) {
261 ✗ G4Exception("G4OCCTAssemblyVolume::ImportLabel", "G4OCCT_Asm003", FatalException,
262 "Simple-shape label carries neither a STEP material attribute nor a part name. "
263 "All shapes must have materials registered in G4OCCTMaterialMap.");
264 ✗ return; // unreachable; silences compiler warning
265 }
266
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 G4Material* material = ctx.materialMap.Resolve(matKey);
267
268 // Determine unique part name.
269
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 G4String rawName = GetLabelName(label);
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (rawName.empty()) {
271 ✗ rawName = "Part";
272 ✗ }
273
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 G4String uniqueName = MakeUniqueName(rawName, ctx.usedNames);
274
275 // Recenter the shape per docs/reference_position.md Strategy C.
276 // The centroid vector records the old centroid position in the original
277 // shape frame; it is composed into the placement below.
278
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 TopoDS_Shape centeredShape = RecenterShape(rawShape, centroid);
279
280
4/10
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
22 auto* solid = new G4OCCTSolid(uniqueName + "_solid", centeredShape);
281
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 lv = new G4OCCTLogicalVolume(solid, material, uniqueName, centeredShape);
282
283 // Cache: store both the logical volume and its centroid so that repeated
284 // instances can reuse the centroid without recomputing the bounding box.
285
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 ctx.prototypeMap.emplace(labelKey, std::make_pair(lv, centroid));
286
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (ctx.logicalVolumes) {
287
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 (*ctx.logicalVolumes)[uniqueName] = lv;
288 22 }
289 22 }
290
291 // Absorb the recentering offset into composedTrsf so that the solid's
292 // local origin (now the centroid) is placed at the correct world position.
293 //
294 // After recentering, a point at the solid's local origin corresponds to
295 // the centroid in the original frame. The effective placement transform
296 // must first shift by `centroid` (from recentered → original local frame)
297 // and then apply `composedTrsf` (original local → world):
298 // T_eff = composedTrsf * Translate(centroid)
299
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 gp_Trsf recenterComp;
300
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 recenterComp.SetTranslation(centroid);
301
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 const gp_Trsf effectiveTrsf = composedTrsf.Multiplied(recenterComp);
302
303
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 auto [rot, trans] = TrsfToG4(effectiveTrsf);
304
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
48 parentAssembly->AddPlacedVolume(lv, trans, &rot);
305 return;
306 24 }
307
308 // Unrecognised label category (e.g. free-shape reference) — skip silently.
309 26 }
310
311 // ── Factory ───────────────────────────────────────────────────────────────────
312
313 16 G4OCCTAssemblyVolume* G4OCCTAssemblyVolume::FromSTEP(const std::string& path,
314 const G4OCCTMaterialMap& materialMap) {
315 // ── Open XDE document and read the STEP file ─────────────────────────────────
316
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
19 Handle(TDocStd_Application) app = new TDocStd_Application;
317
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 Handle(TDocStd_Document) doc;
318
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 app->NewDocument("MDTV-CAF", doc);
319
320
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 STEPCAFControl_Reader cafReader;
321
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 cafReader.SetNameMode(Standard_True);
322
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 cafReader.SetMatMode(Standard_True);
323
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 cafReader.SetColorMode(Standard_True);
324
325
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 2 times.
16 if (cafReader.ReadFile(path.c_str()) != IFSelect_RetDone) {
326
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 throw std::runtime_error("G4OCCTAssemblyVolume::FromSTEP: failed to read STEP file: " + path);
327 }
328
4/6
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 1 time.
14 if (!cafReader.Transfer(doc)) {
329
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.
2 throw std::runtime_error("G4OCCTAssemblyVolume::FromSTEP: failed to transfer STEP document: " +
330 1 path);
331 }
332
333
3/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
334
3/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 Handle(XCAFDoc_MaterialTool) matTool = XCAFDoc_DocumentTool::MaterialTool(doc->Main());
335
336 // ── Collect top-level (free) shape labels ────────────────────────────────────
337
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 TDF_LabelSequence freeShapes;
338
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 shapeTool->GetFreeShapes(freeShapes);
339
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 if (freeShapes.IsEmpty()) {
340 ✗ throw std::runtime_error(
341 ✗ "G4OCCTAssemblyVolume::FromSTEP: STEP file contains no top-level shapes: " + path);
342 }
343
344 // ── Build the Geant4 volume hierarchy ────────────────────────────────────────
345
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 auto* result = new G4OCCTAssemblyVolume;
346
347 13 BuildContext ctx{
348
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 .shapeTool = shapeTool,
349
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 .matTool = matTool,
350 13 .materialMap = materialMap,
351 13 .logicalVolumes = &result->fLogicalVolumes,
352 26 };
353
354
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 const gp_Trsf identity; // starts as the identity transformation
355
3/4
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 13 times.
35 for (Standard_Integer i = 1; i <= freeShapes.Length(); ++i) {
356
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 ImportLabel(freeShapes.Value(i), result, identity, ctx);
357 22 }
358
359 13 return result;
360 19 }
361
362 // ── SD map application ────────────────────────────────────────────────────────
363
364 4 std::size_t G4OCCTAssemblyVolume::ApplySDMap(const G4OCCTSensitiveDetectorMap& sdMap) {
365 4 std::size_t count = 0;
366
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4 times.
13 for (auto& [name, lv] : fLogicalVolumes) {
367 18 G4VSensitiveDetector* sd = sdMap.Resolve(name);
368
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9 if (sd != nullptr) {
369 7 lv->SetSensitiveDetector(sd);
370 7 ++count;
371 7 }
372 }
373 4 return count;
374 }
375