G4OCCT 0.1.0
Geant4 interface to Open CASCADE Technology (OCCT) geometry definitions
Loading...
Searching...
No Matches
main.cc
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-2.1-or-later
2// Copyright (C) 2026 G4OCCT Contributors
3
37
38#include <IFSelect_ReturnStatus.hxx>
39#include <STEPCAFControl_Reader.hxx>
40#include <TDataStd_Name.hxx>
41#include <TDF_LabelSequence.hxx>
42#include <TDocStd_Application.hxx>
43#include <TDocStd_Document.hxx>
44#include <XCAFDoc_DocumentTool.hxx>
45#include <XCAFDoc_MaterialTool.hxx>
46#include <XCAFDoc_ShapeTool.hxx>
47
48#include <algorithm>
49#include <cstdlib>
50#include <cstring>
51#include <filesystem>
52#include <fstream>
53#include <iostream>
54#include <map>
55#include <set>
56#include <sstream>
57#include <stdexcept>
58#include <string>
59#include <vector>
60
61namespace fs = std::filesystem;
62
63// ── OCCT helpers ─────────────────────────────────────────────────────────────
64
66static std::string LabelName(const TDF_Label& label) {
67 Handle(TDataStd_Name) nameAttr;
68 if (label.FindAttribute(TDataStd_Name::GetID(), nameAttr)) {
69 TCollection_ExtendedString ext = nameAttr->Get();
70 TCollection_AsciiString ascii(ext, '?');
71 return std::string(ascii.ToCString());
72 }
73 return {};
74}
75
77static std::string MaterialName(const TDF_Label& label,
78 const Handle(XCAFDoc_MaterialTool) & matTool) {
79 Handle(TCollection_HAsciiString) matName;
80 Handle(TCollection_HAsciiString) matDescription;
81 Standard_Real density{};
82 Handle(TCollection_HAsciiString) densityName;
83 Handle(TCollection_HAsciiString) densityValType;
84 if (matTool->GetMaterial(label, matName, matDescription, density, densityName, densityValType)) {
85 if (!matName.IsNull())
86 return std::string(matName->ToCString());
87 }
88 return {};
89}
90
96static void CollectMaterials(const TDF_Label& label, const Handle(XCAFDoc_ShapeTool) & shapeTool,
97 const Handle(XCAFDoc_MaterialTool) & matTool,
98 std::set<std::string>& out) {
99 if (shapeTool->IsAssembly(label)) {
100 TDF_LabelSequence components;
101 shapeTool->GetComponents(label, components, /*recursive=*/Standard_False);
102 for (Standard_Integer i = 1; i <= components.Length(); ++i) {
103 TDF_Label referred;
104 if (shapeTool->GetReferredShape(components.Value(i), referred))
105 CollectMaterials(referred, shapeTool, matTool, out);
106 else
107 CollectMaterials(components.Value(i), shapeTool, matTool, out);
108 }
109 return;
110 }
111
112 // Solid label: try material attribute first, then label name.
113 std::string key = MaterialName(label, matTool);
114 if (key.empty())
115 key = LabelName(label);
116 if (!key.empty())
117 out.insert(key);
118}
119
120// ── XML helpers ───────────────────────────────────────────────────────────────
121
123static std::string XmlAttr(const std::string& s) {
124 std::string out;
125 out.reserve(s.size());
126 for (char c : s) {
127 switch (c) {
128 case '&':
129 out += "&amp;";
130 break;
131 case '"':
132 out += "&quot;";
133 break;
134 case '<':
135 out += "&lt;";
136 break;
137 case '>':
138 out += "&gt;";
139 break;
140 default:
141 out += c;
142 }
143 }
144 return out;
145}
146
147// ── Existing material map reader (lightweight, no Geant4 dependency) ─────────
148
151static std::string XmlUnescape(std::string s) {
152 struct Ent {
153 const char* escaped;
154 const char* raw;
155 };
156 static constexpr Ent kEntities[] = {
157 {"&amp;", "&"}, {"&quot;", "\""}, {"&lt;", "<"}, {"&gt;", ">"}, {"&apos;", "'"},
158 };
159 for (const auto& e : kEntities) {
160 std::string::size_type pos = 0;
161 while ((pos = s.find(e.escaped, pos)) != std::string::npos) {
162 s.replace(pos, std::strlen(e.escaped), e.raw);
163 pos += std::strlen(e.raw);
164 }
165 }
166 return s;
167}
168
171static std::map<std::string, std::string> ReadExistingMap(const std::string& path) {
172 std::map<std::string, std::string> result;
173 std::ifstream in(path);
174 if (!in.is_open()) {
175 std::cerr << "step2dd4hep: warning: cannot open existing map '" << path << "' — ignored\n";
176 return result;
177 }
178 std::string line;
179 while (std::getline(in, line)) {
180 // Look for: <entry step_name="..." dd4hep_material="..."/>
181 auto spos = line.find("step_name=\"");
182 auto mpos = line.find("dd4hep_material=\"");
183 if (spos == std::string::npos || mpos == std::string::npos)
184 continue;
185 spos += 11;
186 auto send = line.find('"', spos);
187 mpos += 17;
188 auto mend = line.find('"', mpos);
189 if (send == std::string::npos || mend == std::string::npos)
190 continue;
191 std::string stepName = XmlUnescape(line.substr(spos, send - spos));
192 std::string matName = XmlUnescape(line.substr(mpos, mend - mpos));
193 if (!matName.empty() && matName != "TODO")
194 result[stepName] = matName;
195 }
196 return result;
197}
198
199// ── Usage ─────────────────────────────────────────────────────────────────────
200
201static void PrintUsage(const char* prog) {
202 std::cerr << "Usage: " << prog << " [options] geometry.step\n"
203 << "\n"
204 << "Options:\n"
205 << " -o <file> Output compact XML (default: ./<stem>.xml)\n"
206 << " -m <file> Output material-map XML "
207 "(default: ./<stem>_materials.xml)\n"
208 << " --existing-map <file> Existing material-map XML to seed mappings from\n"
209 << " --detector-id <n> Detector id in compact file (default: 1)\n"
210 << " --detector-name <n> Detector element name (default: STEP file stem)\n"
211 << " -h, --help Print this help\n";
212}
213
214// ── main ─────────────────────────────────────────────────────────────────────
215
216int main(int argc, char** argv) {
217 std::string stepPath;
218 std::string compactOut;
219 std::string materialOut;
220 std::string existingMapPath;
221 std::string detectorName;
222 int detectorId = 1;
223
224 // ── Argument parsing ──────────────────────────────────────────────────────
225 for (int i = 1; i < argc; ++i) {
226 std::string arg(argv[i]);
227 if (arg == "-h" || arg == "--help") {
228 PrintUsage(argv[0]);
229 return 0;
230 } else if (arg == "-o") {
231 if (++i >= argc) {
232 std::cerr << "step2dd4hep: -o requires an argument\n";
233 return 1;
234 }
235 compactOut = argv[i];
236 } else if (arg == "-m") {
237 if (++i >= argc) {
238 std::cerr << "step2dd4hep: -m requires an argument\n";
239 return 1;
240 }
241 materialOut = argv[i];
242 } else if (arg == "--existing-map") {
243 if (++i >= argc) {
244 std::cerr << "step2dd4hep: --existing-map requires an argument\n";
245 return 1;
246 }
247 existingMapPath = argv[i];
248 } else if (arg == "--detector-id") {
249 if (++i >= argc) {
250 std::cerr << "step2dd4hep: --detector-id requires an argument\n";
251 return 1;
252 }
253 try {
254 detectorId = std::stoi(argv[i]);
255 } catch (const std::exception& e) {
256 std::cerr << "step2dd4hep: --detector-id requires a valid integer, got '" << argv[i]
257 << "': " << e.what() << "\n";
258 return 1;
259 }
260 } else if (arg == "--detector-name") {
261 if (++i >= argc) {
262 std::cerr << "step2dd4hep: --detector-name requires an argument\n";
263 return 1;
264 }
265 detectorName = argv[i];
266 } else if (arg[0] == '-') {
267 std::cerr << "step2dd4hep: unknown option '" << arg << "'\n";
268 PrintUsage(argv[0]);
269 return 1;
270 } else {
271 if (!stepPath.empty()) {
272 std::cerr << "step2dd4hep: multiple STEP files specified; only one is supported\n";
273 return 1;
274 }
275 stepPath = arg;
276 }
277 }
278
279 if (stepPath.empty()) {
280 PrintUsage(argv[0]);
281 return 1;
282 }
283
284 // ── Derive default output paths ───────────────────────────────────────────
285 fs::path stepFsPath(stepPath);
286 std::string stem = stepFsPath.stem().string();
287
288 if (compactOut.empty())
289 compactOut = (fs::current_path() / (stem + ".xml")).string();
290 if (materialOut.empty())
291 materialOut = (fs::current_path() / (stem + "_materials.xml")).string();
292 if (detectorName.empty())
293 detectorName = stem;
294
295 // ── Read STEP file with OCCT XDE ──────────────────────────────────────────
296 Handle(TDocStd_Application) app = new TDocStd_Application;
297 Handle(TDocStd_Document) doc;
298 app->NewDocument("MDTV-CAF", doc);
299
300 // ── Check file existence before invoking the OCCT reader ─────────────────
301 // Some OCCT builds return IFSelect_RetDone for a missing path (creating an
302 // empty document), so we guard with an explicit existence check first.
303 if (!fs::exists(stepFsPath)) {
304 std::cerr << "step2dd4hep: STEP file not found: '" << stepPath << "'\n";
305 return 1;
306 }
307
308 STEPCAFControl_Reader reader;
309 reader.SetNameMode(Standard_True);
310 reader.SetMatMode(Standard_True);
311
312 IFSelect_ReturnStatus status = reader.ReadFile(stepPath.c_str());
313 if (status != IFSelect_RetDone) {
314 std::cerr << "step2dd4hep: failed to read STEP file '" << stepPath << "'\n";
315 return 1;
316 }
317 if (!reader.Transfer(doc)) {
318 std::cerr << "step2dd4hep: STEP transfer to XDE document failed\n";
319 return 1;
320 }
321
322 Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
323 Handle(XCAFDoc_MaterialTool) matTool = XCAFDoc_DocumentTool::MaterialTool(doc->Main());
324
325 // ── Collect unique material/shape keys ────────────────────────────────────
326 TDF_LabelSequence freeShapes;
327 shapeTool->GetFreeShapes(freeShapes);
328
329 std::set<std::string> stepNames;
330 for (Standard_Integer i = 1; i <= freeShapes.Length(); ++i)
331 CollectMaterials(freeShapes.Value(i), shapeTool, matTool, stepNames);
332
333 if (stepNames.empty()) {
334 std::cerr << "step2dd4hep: warning: no material or label names found in '" << stepPath
335 << "'; the material map will be empty\n";
336 }
337
338 // ── Load existing map (if any) ────────────────────────────────────────────
339 std::map<std::string, std::string> knownMappings;
340 if (!existingMapPath.empty())
341 knownMappings = ReadExistingMap(existingMapPath);
342
343 // ── Generate material-map XML ─────────────────────────────────────────────
344 {
345 std::ofstream out(materialOut);
346 if (!out.is_open()) {
347 std::cerr << "step2dd4hep: cannot write material map to '" << materialOut << "'\n";
348 return 1;
349 }
350 out << "<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->\n"
351 << "<!-- Copyright (C) 2026 G4OCCT Contributors -->\n"
352 << "<!--\n"
353 << " Material map for " << stem << "\n"
354 << " Generated by step2dd4hep from: " << stepPath << "\n"
355 << "\n"
356 << " For each <entry>:\n"
357 << " step_name — the name that appears in the STEP file\n"
358 << " dd4hep_material — the DD4hep / Geant4 NIST material name\n"
359 << " (e.g. \"G4_Al\", \"G4_Fe\", \"G4_AIR\", …)\n"
360 << " Replace every \"TODO\" placeholder with the correct material name.\n"
361 << "-->\n"
362 << "<material_map>\n";
363 for (const auto& name : stepNames) {
364 auto it = knownMappings.find(name);
365 std::string mapped = (it != knownMappings.end()) ? it->second : "TODO";
366 out << " <entry step_name=\"" << XmlAttr(name) << "\""
367 << " dd4hep_material=\"" << XmlAttr(mapped) << "\"/>\n";
368 }
369 out << "</material_map>\n";
370 }
371 std::cout << "step2dd4hep: wrote material map → " << materialOut << "\n";
372
373 // ── Generate compact XML ──────────────────────────────────────────────────
374 // Use an absolute path so the compact file works regardless of cwd.
375 fs::path absStep = fs::absolute(stepFsPath);
376 fs::path absMat = fs::absolute(fs::path(materialOut));
377
378 {
379 std::ofstream out(compactOut);
380 if (!out.is_open()) {
381 std::cerr << "step2dd4hep: cannot write compact XML to '" << compactOut << "'\n";
382 return 1;
383 }
384 out << "<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->\n"
385 << "<!-- Copyright (C) 2026 G4OCCT Contributors -->\n"
386 << "<!--\n"
387 << " DD4hep compact file for " << stem << "\n"
388 << " Generated by step2dd4hep from: " << stepPath << "\n"
389 << "\n"
390 << " Fill in the material map (" << absMat.filename().string() << ")\n"
391 << " before running ddsim or npsim.\n"
392 << "-->\n"
393 << "<lccdd>\n"
394 << " <info name=\"" << XmlAttr(stem) << "\"\n"
395 << " title=\"" << XmlAttr(stem) << "\"\n"
396 << " author=\"\"\n"
397 << " url=\"\"\n"
398 << " status=\"development\"\n"
399 << " version=\"0.0\">\n"
400 << " <comment>Compact geometry generated by step2dd4hep.</comment>\n"
401 << " </info>\n"
402 << "\n"
403 << " <define>\n"
404 << " <constant name=\"world_side\" value=\"10000*mm\"/>\n"
405 << " <constant name=\"world_x\" value=\"world_side/2\"/>\n"
406 << " <constant name=\"world_y\" value=\"world_side/2\"/>\n"
407 << " <constant name=\"world_z\" value=\"world_side/2\"/>\n"
408 << " <constant name=\"tracker_region_rmax\" value=\"world_side/2\"/>\n"
409 << " <constant name=\"tracker_region_zmax\" value=\"world_side/2\"/>\n"
410 << " </define>\n"
411 << "\n"
412 << " <detectors>\n"
413 << " <detector id=\"" << detectorId << "\"\n"
414 << " name=\"" << XmlAttr(detectorName) << "\"\n"
415 << " type=\"G4OCCT_STEPAssembly\">\n"
416 << " <step_file path=\"" << XmlAttr(absStep.string()) << "\"/>\n"
417 << " <position x=\"0\" y=\"0\" z=\"0\"/>\n"
418 << " <material_map>\n"
419 << " <!--\n"
420 << " Auto-generated from STEP material/label names.\n"
421 << " Replace TODO values in " << absMat.filename().string() << "\n"
422 << " and re-run step2dd4hep, or edit the entries below directly.\n"
423 << " -->\n";
424
425 for (const auto& name : stepNames) {
426 auto it = knownMappings.find(name);
427 std::string mapped = (it != knownMappings.end()) ? it->second : "TODO";
428 out << " <entry step_name=\"" << XmlAttr(name) << "\""
429 << " dd4hep_material=\"" << XmlAttr(mapped) << "\"/>\n";
430 }
431
432 out << " </material_map>\n"
433 << " </detector>\n"
434 << " </detectors>\n"
435 << "\n"
436 << "</lccdd>\n";
437 }
438 std::cout << "step2dd4hep: wrote compact XML → " << compactOut << "\n";
439
440 if (!stepNames.empty()) {
441 long nTodo = std::count_if(stepNames.begin(), stepNames.end(), [&](const std::string& n) {
442 return knownMappings.find(n) == knownMappings.end();
443 });
444 if (nTodo > 0)
445 std::cout << "step2dd4hep: " << nTodo << " material(s) need mapping in "
446 << absMat.filename().string() << " (marked TODO)\n";
447 }
448
449 return 0;
450}
int main(int argc, char **argv)
Definition main.cc:130
static std::string MaterialName(const TDF_Label &label, const Handle(XCAFDoc_MaterialTool) &matTool)
Return the XDE material name for label, or empty string.
Definition main.cc:77
static std::string LabelName(const TDF_Label &label)
Return the TDataStd_Name value for label, or empty string.
Definition main.cc:66
static void CollectMaterials(const TDF_Label &label, const Handle(XCAFDoc_ShapeTool) &shapeTool, const Handle(XCAFDoc_MaterialTool) &matTool, std::set< std::string > &out)
Definition main.cc:96
static std::map< std::string, std::string > ReadExistingMap(const std::string &path)
Definition main.cc:171
static std::string XmlAttr(const std::string &s)
Escape characters that must be escaped inside an XML attribute value.
Definition main.cc:123
static void PrintUsage(const char *prog)
Definition main.cc:201
static std::string XmlUnescape(std::string s)
Definition main.cc:151