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