gtree
Loading...
Searching...
No Matches
gtree.cc
Go to the documentation of this file.
1// Implementation of the GTree Qt widget and its internal per-volume cache model.
2// Doxygen documentation is authoritative in the header; this file uses short
3// non-Doxygen comments to summarize behavior.
4
5// c++
6#include <cmath>
7#include <set>
8#include <sstream>
9
10// geant4
11#include "G4BooleanSolid.hh"
12
13// Qt
14#include <QHeaderView>
15#include <QTextEdit>
16#include <QVBoxLayout>
17#include <QColorDialog>
18#include <QDialog>
19#include <QDialogButtonBox>
20#include <QMessageBox>
21#include <QSignalBlocker>
22#include <QStringList>
23#include <QTimer>
24
25// gtree
26#include "gtree.h"
27#include "gtree_options.h"
28
29// gemc
30#include "gsystemConventions.h"
31
32// geant4
33#include "G4VisAttributes.hh"
34#include "G4Material.hh"
35#include "G4VisManager.hh"
36#include "G4VViewer.hh"
37#include "G4VSceneHandler.hh"
38#include "G4VGraphicsSystem.hh"
39#include "G4UImanager.hh"
40#include "gtouchable.h"
41#include "gutilities.h"
42
43namespace {
44
45std::vector<std::string> splitParams(const std::string& s) {
46 std::vector<std::string> result;
47 std::istringstream ss(s);
48 std::string tok;
49 while (std::getline(ss, tok, ',')) {
50 const auto a = tok.find_first_not_of(" \t");
51 const auto b = tok.find_last_not_of(" \t");
52 if (a != std::string::npos)
53 result.push_back(tok.substr(a, b - a + 1));
54 }
55 return result;
56}
57
58using PDesc = std::vector<const char*>;
59
60const PDesc& solidParamDescs(const std::string& solid, std::size_t n) {
61 static const std::unordered_map<std::string, PDesc> kFixed = {
62 {"G4Box", {"dx: half length in x",
63 "dy: half length in y",
64 "dz: half length in z"}},
65 {"G4Tubs", {"rin: inner radius",
66 "rout: outer radius",
67 "length: half length in z",
68 "phi start: starting phi angle",
69 "phi total: total phi angle"}},
70 {"G4Cons", {"rin1: inner radius at -dz",
71 "rout1: outer radius at -dz",
72 "rin2: inner radius at +dz",
73 "rout2: outer radius at +dz",
74 "length: half length in z",
75 "phi start: starting phi angle",
76 "phi total: total phi angle"}},
77 {"G4Trd", {"dx1: half length in x at -dz",
78 "dx2: half length in x at +dz",
79 "dy1: half length in y at -dz",
80 "dy2: half length in y at +dz",
81 "z: half length in z"}},
82 {"G4Sphere", {"rmin: inner radius",
83 "rmax: outer radius",
84 "sphi: starting phi angle",
85 "dphi: delta phi angle",
86 "stheta: starting theta angle",
87 "dtheta: delta theta angle"}},
88 {"G4Paraboloid", {"dz: half length in z",
89 "r1: radius at -dz",
90 "r2: radius at +dz"}},
91 {"G4EllipticalTube", {"dx: half length in x",
92 "dy: half length in y",
93 "dz: half length in z"}},
94 };
95 static const std::unordered_map<std::size_t, PDesc> kTrap = {
96 {4, {"pz: length along Z",
97 "py: length along Y",
98 "px: length along X (wider side)",
99 "pltx: length along X (narrower side)"}},
100 {11, {"pDz: half Z length",
101 "pTheta: polar angle of line joining base centres",
102 "pPhi: azimuthal angle of line joining base centres",
103 "pDy1: half Y length at -dz",
104 "pDx1: half X length at smaller Y, base at -dz",
105 "pDx2: half X length at bigger Y, base at -dz",
106 "pAlp1: angle between Y-axis and centre line at -dz",
107 "pDy2: half Y length at +dz",
108 "pDx3: half X length at smaller Y, base at +dz",
109 "pDx4: half X length at bigger Y, base at +dz",
110 "pAlp2: angle between Y-axis and centre line at +dz"}},
111 };
112 static const PDesc kEmpty;
113
114 if (solid == "G4Trap") {
115 const auto it = kTrap.find(n);
116 return it != kTrap.end() ? it->second : kEmpty;
117 }
118 const auto it = kFixed.find(solid);
119 return it != kFixed.end() ? it->second : kEmpty;
120}
121
122// Format a "number*unit" or bare "number" token: max 3 decimal places,
123// values with |x| < 1e-7 are shown as 0.
124static QString formatVal(const std::string& token) {
125 const auto star = token.find('*');
126 const std::string numPart = (star == std::string::npos) ? token : token.substr(0, star);
127 const std::string unit = (star == std::string::npos) ? "" : token.substr(star);
128 try {
129 std::size_t pos = 0;
130 double v = std::stod(numPart, &pos);
131 if (pos != numPart.size()) return QString::fromStdString(token);
132 if (std::abs(v) < 1e-7) v = 0.0;
133 QString s = QString::number(v, 'f', 3);
134 while (s.endsWith('0') && s.contains('.')) s.chop(1);
135 if (s.endsWith('.')) s.chop(1);
136 return s + QString::fromStdString(unit);
137 } catch (...) {
138 return QString::fromStdString(token);
139 }
140}
141
142QString formatParameters(const std::string& solid, const std::string& paramsStr) {
143 if (paramsStr.empty()) return {};
144 const auto vals = splitParams(paramsStr);
145 if (vals.empty()) return {};
146
147 QString html = "Parameters:";
148
149 // Polycone: fixed header (phiStart, phiTotal, nplanes) then arrays
150 if (solid == "G4Polycone" && vals.size() >= 3) {
151 const char* kPcFixed[3] = {
152 "phi start: starting phi angle",
153 "phi total: total phi angle",
154 "nplanes: number of planes"
155 };
156 for (int i = 0; i < 3 && i < (int)vals.size(); ++i)
157 html += QString("<br>&nbsp;&nbsp;<i>%1</i>: %2").arg(kPcFixed[i], formatVal(vals[i]));
158 int nplanes = 0;
159 try { nplanes = std::stoi(vals[2]); } catch (...) {}
160 int idx = 3;
161 for (int p = 0; p < nplanes && idx < (int)vals.size(); ++p, ++idx)
162 html += QString("<br>&nbsp;&nbsp;<i>z[%1]</i>: %2").arg(p).arg(formatVal(vals[idx]));
163 for (int p = 0; p < nplanes && idx < (int)vals.size(); ++p, ++idx)
164 html += QString("<br>&nbsp;&nbsp;<i>rin[%1]</i>: %2").arg(p).arg(formatVal(vals[idx]));
165 for (int p = 0; p < nplanes && idx < (int)vals.size(); ++p, ++idx)
166 html += QString("<br>&nbsp;&nbsp;<i>rout[%1]</i>: %2").arg(p).arg(formatVal(vals[idx]));
167 return html;
168 }
169
170 // G4Trap from 8 vertices (24 params)
171 if (solid == "G4Trap" && vals.size() == 24) {
172 const char* kAxes[3] = {"x", "y", "z"};
173 for (int v = 0; v < 8; ++v)
174 for (int c = 0; c < 3; ++c)
175 html += QString("<br>&nbsp;&nbsp;<i>v%1%2</i>: %3")
176 .arg(v + 1).arg(kAxes[c]).arg(formatVal(vals[v * 3 + c]));
177 return html;
178 }
179
180 // All other solids (including G4Trap with 4 or 11 params)
181 const auto& descs = solidParamDescs(solid, vals.size());
182 for (std::size_t i = 0; i < vals.size(); ++i) {
183 const QString desc = i < descs.size()
184 ? QString::fromStdString(descs[i])
185 : QString("missing parameters description");
186 html += QString("<br>&nbsp;&nbsp;<i>%1</i>: %2").arg(desc, formatVal(vals[i]));
187 }
188 return html;
189}
190
191std::string resolveBooleanOperandName(const std::string& systemName,
192 const std::string& operand,
193 const std::unordered_map<std::string, const GVolume*>& gvolumes) {
194 if (gvolumes.find(operand) != gvolumes.end()) return operand;
195
196 const std::string qualified = systemName.empty() ? operand : systemName + "/" + operand;
197 if (gvolumes.find(qualified) != gvolumes.end()) return qualified;
198
199 return operand;
200}
201
202std::string leafName(const std::string& volumeName) {
203 const auto slash = volumeName.find_last_of('/');
204 return slash == std::string::npos ? volumeName : volumeName.substr(slash + 1);
205}
206
207QString joinBooleanTokens(const std::vector<std::string>& tokens) {
208 QStringList parts;
209 for (const auto& token : tokens) {
210 parts << QString::fromStdString(token).toHtmlEscaped();
211 }
212 return parts.join(QStringLiteral(" "));
213}
214
215QString formatOperationLine(const std::vector<std::string>& tokens,
216 const std::string& systemName,
217 const std::unordered_map<std::string, const GVolume*>& gvolumes) {
218 if (tokens.size() != 3) return joinBooleanTokens(tokens);
219
220 const auto left = leafName(resolveBooleanOperandName(systemName, tokens[0], gvolumes));
221 const auto right = leafName(resolveBooleanOperandName(systemName, tokens[2], gvolumes));
222 return QString("%1 %2 %3").arg(QString::fromStdString(left).toHtmlEscaped(),
223 QString::fromStdString(tokens[1]).toHtmlEscaped(),
224 QString::fromStdString(right).toHtmlEscaped());
225}
226
227void appendNestedBooleanOperationLines(QString& html,
228 const std::string& volumeName,
229 const std::string& systemName,
230 const std::unordered_map<std::string, const GVolume*>& gvolumes,
231 std::set<std::string>& visited) {
232 const auto fullName = resolveBooleanOperandName(systemName, volumeName, gvolumes);
233 if (!visited.insert(fullName).second) return;
234
235 const auto gvolIt = gvolumes.find(fullName);
236 if (gvolIt == gvolumes.end()) return;
237
238 const auto solidsOpr = gvolIt->second->getSolidsOpr();
239 if (!solidsOpr) return;
240
241 const auto tokens = gutilities::getStringVectorFromString(*solidsOpr);
242 if (tokens.size() != 3) return;
243
244 html += QString("<br>&nbsp;&nbsp;%1 = %2")
245 .arg(QString::fromStdString(leafName(fullName)).toHtmlEscaped(),
246 formatOperationLine(tokens, systemName, gvolumes));
247
248 appendNestedBooleanOperationLines(html, tokens[0], systemName, gvolumes, visited);
249 appendNestedBooleanOperationLines(html, tokens[2], systemName, gvolumes, visited);
250}
251
252QString formatBooleanOperationDescription(const std::string& fullName,
253 const std::string& solidsOpr,
254 const std::unordered_map<std::string, const GVolume*>& gvolumes) {
255 if (gutilities::is_unset(solidsOpr)) return {};
256
257 const auto tokens = gutilities::getStringVectorFromString(solidsOpr);
258 if (tokens.size() != 3) {
259 return QObject::tr("Boolean Operation: %1").arg(QString::fromStdString(solidsOpr).toHtmlEscaped());
260 }
261
262 const auto slash = fullName.find_last_of('/');
263 const std::string systemName = slash == std::string::npos ? "" : fullName.substr(0, slash);
264 QString html = QObject::tr("Boolean Operation: %1").arg(formatOperationLine(tokens, systemName, gvolumes));
265
266 std::set<std::string> visited{fullName};
267 appendNestedBooleanOperationLines(html, tokens[0], systemName, gvolumes, visited);
268 appendNestedBooleanOperationLines(html, tokens[2], systemName, gvolumes, visited);
269 return html;
270}
271
272QString formatMirrorVector(const std::vector<double>& values, double scale = 1.0, const QString& unit = {}) {
273 if (values.empty()) return QObject::tr("Not set");
274
275 QStringList formatted;
276 for (const double value : values) {
277 QString entry = QString::number(value / scale, 'g', 10);
278 if (!unit.isEmpty()) entry += QStringLiteral(" ") + unit;
279 formatted << entry;
280 }
281 return formatted.join(QStringLiteral(", "));
282}
283
284QString formatMirrorProperties(const GMirror& mirror) {
285 QString html = QStringLiteral("<table cellspacing=\"5\">");
286 const auto addRow = [&html](const QString& label, QString value) {
287 if (value.isEmpty()) value = QObject::tr("Not set");
288 value = value.toHtmlEscaped().replace(QStringLiteral("\n"), QStringLiteral("<br>"));
289 html += QStringLiteral("<tr><td valign=\"top\"><b>%1:</b></td><td>%2</td></tr>")
290 .arg(label.toHtmlEscaped(), value);
291 };
292
293 addRow(QObject::tr("System"), QString::fromStdString(mirror.getSystem()));
294 addRow(QObject::tr("Name"), QString::fromStdString(mirror.getName()));
295 addRow(QObject::tr("Description"), QString::fromStdString(mirror.getDescription()));
296 addRow(QObject::tr("Type"), QString::fromStdString(mirror.getType()));
297 addRow(QObject::tr("Finish"), QString::fromStdString(mirror.getFinish()));
298 addRow(QObject::tr("Model"), QString::fromStdString(mirror.getModel()));
299 addRow(QObject::tr("Border"), QString::fromStdString(mirror.getBorder()));
300 addRow(QObject::tr("Material optical properties"), QString::fromStdString(mirror.getMatOptProps()));
301 addRow(QObject::tr("Photon energy"), formatMirrorVector(mirror.getPhotonEnergy(), CLHEP::eV, "eV"));
302 addRow(QObject::tr("Index of refraction"), formatMirrorVector(mirror.getIndexOfRefraction()));
303 addRow(QObject::tr("Reflectivity"), formatMirrorVector(mirror.getReflectivity()));
304 addRow(QObject::tr("Efficiency"), formatMirrorVector(mirror.getEfficiency()));
305 addRow(QObject::tr("Specular lobe"), formatMirrorVector(mirror.getSpecularLobe()));
306 addRow(QObject::tr("Specular spike"), formatMirrorVector(mirror.getSpecularSpike()));
307 addRow(QObject::tr("Backscatter"), formatMirrorVector(mirror.getBackscatter()));
308 addRow(QObject::tr("Transmittance"), formatMirrorVector(mirror.getTransmittance()));
309 addRow(QObject::tr("Sigma alpha"),
310 mirror.hasSigmaAlpha() ? QString::number(mirror.getSigmaAlpha(), 'g', 10) : QObject::tr("Not set"));
311
312 return html + QStringLiteral("</table>");
313}
314
315} // anonymous namespace
316
317
318// Cache a volume's hierarchy, material, visualization attributes, and pygemc descriptor.
319G4Ttree_item::G4Ttree_item(G4Volume* g4volume, const GVolume* gvolume) {
320 if (g4volume == nullptr || g4volume->getLogical() == nullptr || g4volume->getSolid() == nullptr) {
321 mother = gsystem::MOTHEROFUSALL;
322 material = "unavailable";
323 color = QColor::fromRgbF(1.0, 1.0, 1.0);
324 opacity = 1.0;
325 is_visible = false;
326 return;
327 }
328
329 auto pvolume = g4volume->getPhysical();
330 auto lvolume = g4volume->getLogical();
331 auto svolume = g4volume->getSolid();
332
333 std::string lname = lvolume->GetName();
334 if (lname != gsystem::ROOTWORLDGVOLUMENAME) {
335 auto mlvolume = pvolume != nullptr ? pvolume->GetMotherLogical() : nullptr;
336 mother = mlvolume != nullptr ? mlvolume->GetName() : gsystem::MOTHEROFUSALL;
337 material = lvolume->GetMaterial()->GetName();
338 }
339 else {
340 mother = gsystem::MOTHEROFUSALL;
341 material = "G4_Galactic";
342 }
343
344 // Read visualization attributes from the logical volume.
345 auto visAttributes = lvolume->GetVisAttributes();
346 if (visAttributes != nullptr) {
347 auto gcolor = visAttributes->GetColour();
348 color = QColor::fromRgbF(gcolor.GetRed(), gcolor.GetGreen(), gcolor.GetBlue());
349 opacity = gcolor.GetAlpha();
350 is_visible = visAttributes->IsVisible();
351 }
352 else {
353 // No vis attributes assigned (e.g. imported CAD/GDML volumes): fall back to opaque white, visible.
354 color = QColor::fromRgbF(1.0, 1.0, 1.0);
355 opacity = 1.0;
356 is_visible = true;
357 }
358
359 // Physics quantities (mass, volume, density) are computed lazily on first
360 // request: see compute_physics_quantities().
361 logical_ptr = lvolume;
362 solid_ptr = svolume;
363
364 if (gvolume) {
365 solidType = gvolume->getType();
366 parameters = gvolume->getParameters().value_or("");
367 position = gvolume->getPos();
368 rotation = gvolume->getRot();
369 motherVolume = gvolume->getMotherName();
370 solidsOpr = gvolume->getSolidsOpr().value_or("");
371 volDescription = gvolume->getDescription();
372 mirrorName = gvolume->getMirror().value_or("");
373 }
374}
375
376
377// Compute mass, volume, and density on first request. Geant4 estimates a boolean
378// solid's cubic volume by Monte Carlo with 1M points by default — seconds per solid —
379// so boolean solids use a bounded 100k-point estimate (~1% accuracy, plenty for
380// display), and the mass is not propagated to daughters (whose solids may themselves
381// be booleans). This keeps volume selection interactive on boolean-heavy systems.
382void G4Ttree_item::compute_physics_quantities() const {
383 if (physics_computed) return;
384 physics_computed = true;
385 if (logical_ptr == nullptr || solid_ptr == nullptr) return;
386
387 if (dynamic_cast<G4BooleanSolid*>(solid_ptr) != nullptr) {
388 volume = solid_ptr->EstimateCubicVolume(100000, 0.001) / CLHEP::cm3;
389 density = logical_ptr->GetMaterial()->GetDensity() / (CLHEP::g / CLHEP::cm3);
390 mass = volume * density;
391 }
392 else {
393 volume = solid_ptr->GetCubicVolume() / CLHEP::cm3;
394 mass = logical_ptr->GetMass(false, false) / CLHEP::g;
395 density = volume > 0 ? mass / volume : 0.0;
396 }
397}
398
399
400// Extract a short display name from a full "system/volume" name.
401std::string G4Ttree_item::vname_from_v4name(std::string v4name) {
402 // return name after '/'
403 return v4name.substr(v4name.find_last_of('/') + 1);
404}
405
406// Extract the system prefix from a full "system/volume" name.
407std::string G4Ttree_item::system_from_v4name(std::string v4name) {
408 // return name before '/'
409 return v4name.substr(0, v4name.find_last_of('/'));
410}
411
412
413// Construct the widget, build the internal model, create the UI, and connect signals.
414GTree::GTree(const std::shared_ptr<GOptions>& gopt,
415 std::unordered_map<std::string, G4Volume*> g4volumes_map,
416 std::unordered_map<std::string, const GVolume*> gvolumes_map_in,
417 QWidget* parent,
418 std::unordered_map<std::string, const GMirror*> gmirrors_map_in) :
419 QWidget(parent),
420 GBase(gopt, GTREE_LOGGER),
421 gvolumes_map(std::move(gvolumes_map_in)),
422 gmirrors_map(std::move(gmirrors_map_in)) {
423 // Build the internal representation used to populate the UI tree.
424 build_tree(g4volumes_map);
425
426 // create the UI
427 treeWidget = new QTreeWidget(this);
428 treeWidget->setColumnCount(3);
429 QStringList headers;
430 headers << "Visibility" << "Color" << "Name";
431 treeWidget->setHeaderLabels(headers);
432 treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
433 treeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
434 treeWidget->header()->setSectionResizeMode(2, QHeaderView::Stretch);
435 treeWidget->setRootIsDecorated(true);
436 treeWidget->setAlternatingRowColors(true);
437
438 auto* mainLayout = new QHBoxLayout(this);
439 mainLayout->addWidget(treeWidget, /*stretch*/ 3);
440
441 // Right: property panel has ~same width. Increase stretch to increase
442 rightPanel = right_widget();
443 mainLayout->addWidget(rightPanel, /*stretch*/ 3);
444
445 setLayout(mainLayout);
446
447 // populate the tree from g4_systems_tree
448 populateTree();
449
450 // react to checkboxes
451 connect(treeWidget, &QTreeWidget::itemChanged,
452 this, &GTree::onItemChanged);
453
454
455 // react to clicks / selection to update the right panel
456 connect(treeWidget, &QTreeWidget::itemClicked,
457 this, &GTree::onTreeItemClicked);
458
459 connect(treeWidget, &QTreeWidget::currentItemChanged,
460 this, &GTree::onCurrentItemChanged);
461
462 // connect GQTButtonsWidget signal button_pressed to slot changeStyle()
463 connect(styleButtons->buttonsWidget,
464 SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem*)),
465 this, SLOT(changeStyle()));
466
467
468 // connect slider to slot
469 connect(opacitySlider, &QSlider::valueChanged,
470 this, &GTree::onOpacitySliderChanged);
471
472
473 log->debug(NORMAL, SFUNCTION_NAME, "GTree added");
474}
475
476// Build the Qt tree view from the internal system/volume model.
477void GTree::populateTree() {
478 // for each system
479 for (auto& [systemName, volMap] : g4_systems_tree) {
480 // top-level item for the system
481 auto* systemItem = new QTreeWidgetItem(treeWidget);
482 systemItem->setText(2, QString::fromStdString(systemName));
483 systemItem->setFlags(systemItem->flags() | Qt::ItemIsUserCheckable);
484 systemItem->setCheckState(0, Qt::Checked);
485
486 // We'll build the volume hierarchy inside this system using a lookup map.
487 std::map<std::string, QTreeWidgetItem*> itemLookup;
488
489 // --------------------------------------------------------------------
490 // 1st pass: create items WITHOUT parents, configure text/data/checkbox
491 // --------------------------------------------------------------------
492 for (auto& [volName, vptr] : volMap) {
493 const G4Ttree_item* vitem = vptr.get();
494
495 auto* item = new QTreeWidgetItem; // no parent yet
496 item->setText(2, QString::fromStdString(G4Ttree_item::vname_from_v4name(volName)));
497 item->setData(2, Qt::UserRole, QString::fromStdString(volName)); // store full v4 name
498
499 // checkbox for visibility:
500 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
501 item->setCheckState(0, vitem->get_visibility() ? Qt::Checked : Qt::Unchecked);
502
503 itemLookup[volName] = item;
504 }
505
506 // --------------------------------------------------------------------
507 // 2nd pass: attach items to their parent (system or mother)
508 // --------------------------------------------------------------------
509 for (auto& [volName, vptr] : volMap) {
510 const G4Ttree_item* vitem = vptr.get();
511 auto mother = vitem->get_mother(); // full v4 name of mother
512 auto* thisItem = itemLookup[volName];
513
514 QTreeWidgetItem* parentItem = systemItem;
515
516 // If a mother exists and is present in this system's lookup, attach under it.
517 if (!mother.empty() && mother != "root") {
518 auto itM = itemLookup.find(mother);
519 if (itM != itemLookup.end()) {
520 parentItem = itM->second;
521 }
522 }
523
524 parentItem->addChild(thisItem);
525 }
526
527 // --------------------------------------------------------------------
528 // 3rd pass: create color buttons now that items are in the tree
529 // --------------------------------------------------------------------
530 for (auto& [volName, vptr] : volMap) {
531 const G4Ttree_item* vitem = vptr.get();
532 QTreeWidgetItem* item = itemLookup[volName];
533
534 auto* colorBtn = new QPushButton(treeWidget);
535 QColor c = vitem->get_color();
536 colorBtn->setFixedSize(20, 20);
537 colorBtn->setFlat(true); // no 3D/bevel look
538 colorBtn->setText(QString()); // no text
539
540 colorBtn->setStyleSheet(
541 QString("QPushButton { background-color: %1; border: 1px solid %2; }")
542 .arg(c.name(), palette().color(QPalette::Mid).name())
543 );
544
545 // Store the full volume name as a property so the slot can retrieve it.
546 colorBtn->setProperty("volumeName", QString::fromStdString(volName));
547 connect(colorBtn, &QPushButton::clicked, this, &GTree::onColorButtonClicked);
548
549 treeWidget->setItemWidget(item, 1, colorBtn);
550 }
551 }
552
553 treeWidget->expandAll();
554}
555
556
557// Build the internal system/volume model from the provided volume map.
558void GTree::build_tree(std::unordered_map<std::string, G4Volume*> g4volumes_map) {
559 // loop over map
560 for (auto [name, g4volume] : g4volumes_map) {
561 if (g4volume == nullptr || g4volume->getLogical() == nullptr ||
562 g4volume->getSolid() == nullptr || g4volume->getPhysical() == nullptr) {
563 log->info(2, "Skipping non-placed helper volume <", name, "> from tree");
564 continue;
565 }
566
567 // skip the Geant4 world / gsystem::ROOTWORLDGVOLUMENAME
568 // auto lvolume = g4volume->getLogical();
569 //
570 // if (lvolume && lvolume->GetName() == gsystem::ROOTWORLDGVOLUMENAME) {
571 // log->info(2, "Skipping world volume >", name, "< from tree");
572 // continue;
573 // }
574
575 auto system_name = G4Ttree_item::system_from_v4name(name);
576
577 // ensure the system exists
578 auto& system_tree = g4_systems_tree[system_name];
579 const GVolume* gvol = nullptr;
580 auto it = gvolumes_map.find(name);
581 if (it != gvolumes_map.end()) gvol = it->second;
582 system_tree[name] = std::make_unique<G4Ttree_item>(g4volume, gvol);
583
584 // do not log mass/volume/density here: reading them triggers the (potentially
585 // Monte-Carlo) physics computation for every volume in the tree
586 log->info(2, "Adding ", name, " to tree, system_name is ", system_name);
587 }
588}
589
590// Apply visibility checkbox changes to the selected item and its direct children.
591void GTree::onItemChanged(QTreeWidgetItem* item, int column) {
592 if (column != 0) return; // we care about visibility column only
593
594 // is this a volume item? (has stored v4 name in UserRole)
595 QVariant v = item->data(2, Qt::UserRole);
596
597 // --------------------------------------------------------------------
598 // SYSTEM item: no UserRole data → propagate to direct daughters
599 // --------------------------------------------------------------------
600 if (!v.isValid()) {
601 bool visible = (item->checkState(0) == Qt::Checked);
602
603 QSignalBlocker blocker(treeWidget); // avoid recursive itemChanged
604
605 const int nChildren = item->childCount();
606 for (int i = 0; i < nChildren; ++i) {
607 QTreeWidgetItem* child = item->child(i);
608 QVariant cv = child->data(2, Qt::UserRole);
609 if (!cv.isValid())
610 continue; // skip non-volume children (shouldn't happen)
611
612 const QString fullName = cv.toString();
613
614 // sync child checkbox
615 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
616
617 // apply to Geant4
618 set_visibility(fullName.toStdString(), visible);
619 }
620
621 return;
622 }
623
624 // --------------------------------------------------------------------
625 // VOLUME item: apply to itself and its direct daughter volumes
626 // --------------------------------------------------------------------
627 const QString fullName = v.toString();
628 bool visible = (item->checkState(0) == Qt::Checked);
629
630 QSignalBlocker blocker(treeWidget); // avoid recursive itemChanged
631
632 // 1) apply to this volume
633 set_visibility(fullName.toStdString(), visible);
634
635 // 2) apply same visibility to its direct daughters
636 const int nChildren = item->childCount();
637 for (int i = 0; i < nChildren; ++i) {
638 QTreeWidgetItem* child = item->child(i);
639 QVariant cv = child->data(2, Qt::UserRole);
640 if (!cv.isValid())
641 continue; // skip if not a volume
642
643 const QString childName = cv.toString();
644
645 // sync child checkbox
646 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
647
648 // apply to Geant4
649 set_visibility(childName.toStdString(), visible);
650 }
651}
652
653
654// Open the color dialog and apply a new RGB color to the selected volume.
655void GTree::onColorButtonClicked() {
656 auto* btn = qobject_cast<QPushButton*>(sender());
657 if (!btn)
658 return;
659
660 const QString volName = btn->property("volumeName").toString();
661 if (volName.isEmpty())
662 return;
663
664 QColor initial = Qt::white;
665
666 QColor c = QColorDialog::getColor(initial, this, tr("Select color"));
667 if (!c.isValid())
668 return;
669
670 // Update button appearance
671 btn->setStyleSheet(
672 QString("QPushButton { background-color: %1; border: 1px solid %2; }")
673 .arg(c.name(), palette().color(QPalette::Mid).name())
674 );
675
676 // tell your model
677 set_color(volName.toStdString(), c);
678}
679
680
681// Send a Geant4 UI command to toggle visibility for a specific volume.
682void GTree::set_visibility(const std::string& volumeName, bool visible) {
683 std::string vis_int = visible ? "1" : "0";
684
685 std::string command = "/vis/geometry/set/visibility " + volumeName + " -1 " + vis_int;
686
687 // World visibility uses a different depth value.
688 if (volumeName == gsystem::ROOTWORLDGVOLUMENAME) {
689 command = "/vis/geometry/set/visibility " + volumeName + " 0 " + vis_int;
690 }
691
693}
694
695// Send a Geant4 UI command to set RGB color for a specific volume.
696void GTree::set_color(const std::string& volumeName, const QColor& c) {
697 G4Ttree_item* item = findTreeItem(volumeName);
698 double currentOpacity = item ? item->get_opacity() : 1.0;
699 if (item) item->set_color(c);
700
701 std::string command = "/vis/geometry/set/colour " + volumeName + " 0 "
702 + std::to_string(c.redF()) + " "
703 + std::to_string(c.greenF()) + " "
704 + std::to_string(c.blueF()) + " "
705 + std::to_string(currentOpacity);
706
708}
709
710// Return number of direct children in the Qt tree for the given item.
711int GTree::get_ndaughters(QTreeWidgetItem* item) const {
712 if (!item) return 0;
713 return item->childCount();
714}
715
716// Find the cached model record for a volume by its full name.
717G4Ttree_item* GTree::findTreeItem(const std::string& fullName) {
718 for (const auto& [systemName, volMap] : g4_systems_tree) {
719 auto it = volMap.find(fullName);
720 if (it != volMap.end()) {
721 return it->second.get();
722 }
723 }
724 return nullptr;
725}
726
727
728// Update the right-side panel according to the clicked item.
729void GTree::onTreeItemClicked(QTreeWidgetItem* item, int /*column*/) {
730 if (!bottomPanel)
731 return;
732
733 if (!item) {
734 bottomPanel->setVisible(false);
735 current_volume_name.clear();
736 return;
737 }
738
739 bottomPanel->setVisible(true);
740
741 // Is it a volume? (volume items store the full v4 name in UserRole)
742 QVariant v = item->data(2, Qt::UserRole);
743 bool isVolume = v.isValid();
744
745 // Type label
746 if (isVolume) {
747 typeLabel->setText(QStringLiteral("<b>G4 Volume</b>"));
748 current_volume_name = v.toString().toStdString();
749 }
750 else {
751 typeLabel->setText(QStringLiteral("<b>System</b>"));
752 current_volume_name.clear();
753 }
754
755 // Number of direct daughters
756 int nd = get_ndaughters(item);
757 daughtersLabel->setText(tr("Daughters: %1").arg(nd));
758
759 // Name (column 2 text)
760 QString itemName = item->text(2);
761 nameLabel->setText(tr("Name: %1").arg(itemName));
762
763 // Material / density / mass
764 if (isVolume) {
765 styleButtons->setVisible(true);
766 if (mirrorButton) mirrorButton->setVisible(false);
767
768 const std::string fullName = v.toString().toStdString();
769 const G4Ttree_item* titem = findTreeItem(fullName);
770
771 if (titem) {
772 materialLabel->setText(
773 tr("Material: %1").arg(QString::fromStdString(titem->get_material()))
774 );
775 auto mass = titem->get_mass();
776 if (mass < 1000) {
777 massLabel->setText(tr("Mass: %1 g").arg(mass));
778 }
779 else {
780 massLabel->setText(tr("Mass: %1 kg").arg(mass / 1000));
781 }
782 auto volume = titem->get_volume();
783 if (volume < 1000000) {
784 volumeLabel->setText(tr("Volume: %1 cm3").arg(volume));
785 }
786 else {
787 volumeLabel->setText(tr("Volume: %1 m3").arg(volume / 1000000));
788 }
789
790 densityLabel->setText(
791 tr("Density: %1 g / cm3").arg(titem->get_density())
792 );
793
794 // pygemc descriptor fields
795 const auto solidT = titem->get_solidType();
796 solidTypeLabel->setText(solidT.empty() ? QString()
797 : tr("Solid: %1").arg(QString::fromStdString(solidT)));
798 const auto params = titem->get_parameters();
799 const bool hasParams = !params.empty();
800 parametersLabel->setVisible(hasParams);
801 if (hasParams) parametersLabel->setHtml(formatParameters(solidT, params));
802 const auto pos = titem->get_position();
803 positionLabel->setText(pos.empty() ? QString()
804 : tr("Position: %1").arg(QString::fromStdString(pos)));
805 const auto rot = titem->get_rotation();
806 rotationLabel->setText(rot.empty() ? QString()
807 : tr("Rotation: %1").arg(QString::fromStdString(rot)));
808 const auto mom = titem->get_motherVolume();
809 motherLabel->setText(mom.empty() ? QString() : tr("Mother: %1").arg(QString::fromStdString(mom)));
810 const auto boolDesc =
811 formatBooleanOperationDescription(fullName, titem->get_solidsOpr(), gvolumes_map);
812 if (!boolDesc.isEmpty()) {
813 descriptionLabel->setText(boolDesc);
814 }
815 else {
816 const auto desc = titem->get_volDescription();
817 descriptionLabel->setText(desc.empty() ? QString()
818 : tr("Description: %1")
819 .arg(QString::fromStdString(desc)));
820 }
821
822 const auto mirrorName = titem->get_mirrorName();
823 const bool hasMirror = !gutilities::is_unset(mirrorName) && mirrorName != "no" &&
824 mirrorName != "none";
825 if (mirrorButton && hasMirror) {
826 const auto slash = fullName.find_last_of('/');
827 const std::string systemName = slash == std::string::npos ? "" : fullName.substr(0, slash);
828 const std::string mirrorKey = systemName + "/" + mirrorName;
829 if (gmirrors_map.find(mirrorKey) != gmirrors_map.end()) {
830 mirrorButton->setText(tr("Mirror: %1").arg(QString::fromStdString(mirrorName)));
831 mirrorButton->setProperty("mirrorKey", QString::fromStdString(mirrorKey));
832 mirrorButton->setVisible(true);
833 }
834 }
835
836 // Sync the slider position to the cached alpha channel.
837 double op = titem->get_opacity();
838 int sliderVal = static_cast<int>(op * 100.0 + 0.5);
839 {
840 QSignalBlocker blocker(opacitySlider);
841 opacitySlider->setValue(sliderVal);
842 }
843 opacityLabel->setText(QString::number(op, 'f', 2));
844 opacitySlider->setVisible(true);
845 }
846
847 // Update the inspect and draw-overlaps buttons with the selected volume's leaf name.
848 if (inspectButton) {
849 const QString leaf = QString::fromStdString(G4Ttree_item::vname_from_v4name(current_volume_name));
850 inspectButton->setText(tr("Inspect %1").arg(leaf));
851 inspectButton->setVisible(true);
852 }
853 if (drawOverlapsButton) {
854 const QString leaf = QString::fromStdString(G4Ttree_item::vname_from_v4name(current_volume_name));
855 drawOverlapsButton->setText(tr("Draw Logical Overlaps %1").arg(leaf));
856 drawOverlapsButton->setVisible(true);
857 }
858 }
859 else {
860 styleButtons->setVisible(false);
861 opacitySlider->setVisible(false);
862 if (mirrorButton) mirrorButton->setVisible(false);
863 if (inspectButton) inspectButton->setVisible(false);
864 if (drawOverlapsButton) drawOverlapsButton->setVisible(false);
865 // Systems don't have a single material etc.
866 materialLabel->setText(tr(""));
867 massLabel->setText(tr(""));
868 volumeLabel->setText(tr(""));
869 densityLabel->setText(tr(""));
870 solidTypeLabel->setText(tr(""));
871 parametersLabel->clear();
872 parametersLabel->setVisible(false);
873 positionLabel->setText(tr(""));
874 rotationLabel->setText(tr(""));
875 motherLabel->setText(tr(""));
876 descriptionLabel->setText(tr(""));
877 }
878}
879
880// Keep the right-side panel updated when selection changes via keyboard navigation.
881void GTree::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) {
882 Q_UNUSED(previous);
883 if (!current)
884 return;
885
886 // reset styleButtons
887 styleButtons->reset_buttons();
888
889 // Reuse the same logic as mouse clicks
890 onTreeItemClicked(current, 0);
891}
892
893// Apply a representation command based on the currently selected style button.
894void GTree::changeStyle() {
895
896 // No-op if no volume is selected (system selected or nothing selected).
897 if (current_volume_name.empty())
898 return;
899
900 int button_index = styleButtons->button_pressed();
901
902 if (button_index == 3) {
903 // Action button: deselect it immediately (it is not a persistent style toggle).
904 {
905 QSignalBlocker blocker(styleButtons->buttonsWidget);
906 styleButtons->reset_buttons();
907 }
908 centreTwinkle();
909 return;
910 }
911
912 std::string command;
913
914 if (button_index == 0) {
915 command = "/vis/geometry/set/forceWireframe " + current_volume_name + " 0 1 ";
916 }
917 else if (button_index == 1) {
918 command = "/vis/geometry/set/forceSolid " + current_volume_name + " 0 1 ";
919 }
920 else if (button_index == 2) {
921 command = "/vis/geometry/set/forceCloud " + current_volume_name + " 0 1 ";
922 }
923 else {
924 // Unknown button index: avoid issuing an empty or malformed UI command.
925 return;
926 }
927
929}
930
931
932// Centre the viewer on the selected volume and kick off the twinkle animation.
933void GTree::centreTwinkle() {
934 G4Ttree_item* item = findTreeItem(current_volume_name);
935 if (!item) return;
936
937 twinkleVolumeName = current_volume_name;
938 twinkleSavedColor = item->get_color();
939 twinkleSavedOpacity = item->get_opacity();
940 twinkleTick = 0;
941
942 gutilities::apply_uimanager_commands("/vis/viewer/centreOn " + twinkleVolumeName);
943
944 if (!twinkleTimer) {
945 twinkleTimer = new QTimer(this);
946 connect(twinkleTimer, &QTimer::timeout, this, &GTree::onTwinkleStep);
947 }
948 if (twinkleTimer->isActive())
949 twinkleTimer->stop();
950
951 twinkleTimer->start(180);
952}
953
954
955// Cycle through flash colors, then restore the original colour and alpha.
956void GTree::onTwinkleStep() {
957 constexpr int kSteps = 5;
958 static const QColor kFlash[kSteps] = {
959 QColor(255, 50, 50), // red
960 QColor(255, 220, 0), // yellow
961 QColor( 50, 255, 50), // green
962 QColor( 0, 220, 255), // cyan
963 QColor(220, 50, 255), // magenta
964 };
965
966 if (twinkleTick < kSteps) {
967 set_color(twinkleVolumeName, kFlash[twinkleTick]);
968 ++twinkleTick;
969 } else {
970 twinkleTimer->stop();
971 G4Ttree_item* item = findTreeItem(twinkleVolumeName);
972 if (item) {
973 item->set_color(twinkleSavedColor);
974 item->set_opacity(twinkleSavedOpacity);
975 }
976 const double r = twinkleSavedColor.redF();
977 const double g = twinkleSavedColor.greenF();
978 const double b = twinkleSavedColor.blueF();
979 const std::string cmd = "/vis/geometry/set/colour " + twinkleVolumeName + " 0 "
980 + std::to_string(r) + " "
981 + std::to_string(g) + " "
982 + std::to_string(b) + " "
983 + std::to_string(twinkleSavedOpacity);
985 }
986}
987
988
989// Open the selected volume in a new viewer window of the same driver type.
990void GTree::inspectVolume() {
991 if (current_volume_name.empty()) return;
992
993 G4UImanager* uim = G4UImanager::GetUIpointer();
994 if (!uim) return;
995
996 // Resolve the active driver and save the viewer name so we can return focus
997 // to the original window after opening the inspect window.
998 std::string driverName = "OGLSQt";
999 std::string originalViewerName;
1000 auto* vm = G4VisManager::GetInstance();
1001 if (vm) {
1002 const auto* viewer = vm->GetCurrentViewer();
1003 if (viewer) {
1004 originalViewerName = viewer->GetName();
1005 const auto* sh = viewer->GetSceneHandler();
1006 if (sh) {
1007 const auto* gs = sh->GetGraphicsSystem();
1008 if (gs) driverName = gs->GetNickname();
1009 }
1010 }
1011 }
1012
1013 const std::string leafName = G4Ttree_item::vname_from_v4name(current_volume_name);
1014
1015 // Open a new window, populate it with only the target volume, and flush.
1016 // /vis/open creates a new viewer whose scene handler attaches to the current (full) scene.
1017 // /vis/scene/create makes a new empty scene but does NOT re-attach the handler.
1018 // /vis/sceneHandler/attach must come before /vis/scene/add/volume to wire the new handler
1019 // to the new empty scene; otherwise the first flush renders the full detector.
1020 uim->ApplyCommand("/vis/open " + driverName);
1021 uim->ApplyCommand("/vis/scene/create");
1022 uim->ApplyCommand("/vis/sceneHandler/attach");
1023 uim->ApplyCommand("/vis/scene/add/volume " + current_volume_name + " -1");
1024 uim->ApplyCommand("/vis/viewer/set/background 1 1 1 1");
1025 uim->ApplyCommand("/vis/viewer/set/lineSegmentsPerCircle 100");
1026 // 2D label: large black text centred near the top of the window.
1027 // "! !" tells Geant4 to use the colour and layout set by /vis/set/text*.
1028 uim->ApplyCommand("/vis/set/textColour black");
1029 uim->ApplyCommand("/vis/set/textLayout centre");
1030 uim->ApplyCommand("/vis/scene/add/text2D 0 0.85 36 ! ! " + leafName);
1031 uim->ApplyCommand("/vis/viewer/flush");
1032
1033 // Return Geant4's "current viewer" to the original window so all GUI controls
1034 // (camera, scene properties, tree widget) continue to act on the main view.
1035 if (!originalViewerName.empty())
1036 uim->ApplyCommand("/vis/viewer/select " + originalViewerName);
1037}
1038
1039// Display all properties of the mirror associated with the selected volume.
1040void GTree::showMirrorProperties() {
1041 if (!mirrorButton) return;
1042
1043 const std::string mirrorKey = mirrorButton->property("mirrorKey").toString().toStdString();
1044 const auto mirrorIt = gmirrors_map.find(mirrorKey);
1045 if (mirrorIt == gmirrors_map.end() || mirrorIt->second == nullptr) return;
1046
1047 const GMirror& mirror = *mirrorIt->second;
1048 QDialog dialog(this);
1049 dialog.setWindowTitle(tr("Mirror: %1").arg(QString::fromStdString(mirror.getName())));
1050 dialog.setModal(true);
1051
1052 auto* layout = new QVBoxLayout(&dialog);
1053 auto* properties = new QTextEdit(&dialog);
1054 properties->setReadOnly(true);
1055 properties->setHtml(formatMirrorProperties(mirror));
1056 layout->addWidget(properties);
1057
1058 auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
1059 connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
1060 layout->addWidget(buttons);
1061
1062 dialog.resize(650, 520);
1063 dialog.exec();
1064}
1065
1066
1067// Warn that /vis/drawLogicalVolume is not yet usable due to a Geant4 11.4.2 TOOLSSG bug.
1068void GTree::drawOverlapsWarning() {
1069 QMessageBox::warning(this,
1070 tr("Not yet implemented"),
1071 tr("Draw Logical Overlaps will be implemented when Geant4 fixes the\n"
1072 "G4ToolsSGSceneHandler::GetOrCreateNode \"World mis-match\" crash\n"
1073 "triggered by /vis/drawLogicalVolume in Geant4 11.4.2."));
1074}
1075
1076
1077// Convert slider position to alpha and apply it to the currently selected volume.
1078void GTree::onOpacitySliderChanged(int value) {
1079 if (current_volume_name.empty())
1080 return; // no volume selected
1081
1082 double opacity = value / 100.0;
1083
1084 if (opacityLabel) {
1085 opacityLabel->setText(QString::number(opacity, 'f', 2));
1086 }
1087
1088 set_opacity(current_volume_name, opacity);
1089}
1090
1091// Update alpha for a volume while preserving cached RGB components.
1092void GTree::set_opacity(const std::string& volumeName, double opacity) {
1093
1094 // find current color for this volume from our tree model
1095 G4Ttree_item* item = findTreeItem(volumeName);
1096 if (!item) return;
1097
1098 QColor c = item->get_color();
1099 double r = c.redF();
1100 double g = c.greenF();
1101 double b = c.blueF();
1102
1103 // Geant4: r g b alpha
1104 std::string command = "/vis/geometry/set/colour " + volumeName + " 0 "
1105 + std::to_string(r) + " "
1106 + std::to_string(g) + " "
1107 + std::to_string(b) + " "
1108 + std::to_string(opacity);
1109
1110 // Keep the model synchronized with the command we are sending.
1111 item->set_color(c);
1112 item->set_opacity(opacity);
1113
1115}
Lightweight per-volume record used by GTree to populate the UI.
Definition gtree.h:44
std::string get_mirrorName() const
Return the associated mirror name.
Definition gtree.h:225
std::string get_solidsOpr() const
Return the boolean solid operation descriptor.
Definition gtree.h:221
std::string get_motherVolume() const
Return the mother volume name.
Definition gtree.h:219
static std::string vname_from_v4name(std::string v4name)
Extract the "leaf" volume name from a full volume name.
Definition gtree.cc:401
double get_opacity() const
Return the cached opacity (alpha) in [0,1].
Definition gtree.h:184
std::string get_volDescription() const
Return the volume description.
Definition gtree.h:223
std::string get_rotation() const
Return the placement rotation string with units.
Definition gtree.h:217
double get_density() const
Return the density, computing it on first request.
Definition gtree.h:199
void set_color(const QColor &c)
Update the cached color.
Definition gtree.h:241
void set_opacity(double opacity)
Update the cached opacity.
Definition gtree.h:247
double get_volume() const
Return the volume, computing it on first request.
Definition gtree.h:193
bool get_visibility() const
Return the cached visibility state.
Definition gtree.h:178
double get_mass() const
Return the mass, computing it on first request.
Definition gtree.h:187
QColor get_color() const
Return the cached RGB color.
Definition gtree.h:175
std::string get_solidType() const
Return the solid type string (e.g. "G4Box").
Definition gtree.h:211
static std::string system_from_v4name(std::string v4name)
Extract the system name from a full volume name.
Definition gtree.cc:407
std::string get_position() const
Return the placement position string with units.
Definition gtree.h:215
std::string get_parameters() const
Return the solid parameters string with units.
Definition gtree.h:213
std::string get_material() const
Return the cached material name.
Definition gtree.h:205
G4Ttree_item(G4Volume *g4volume, const GVolume *gvolume=nullptr)
Construct a cached record for a single geometry volume.
Definition gtree.cc:319
std::string get_mother() const
Return the cached mother name.
Definition gtree.h:172
G4VSolid * getSolid() const noexcept
G4VPhysicalVolume * getPhysical() const noexcept
G4LogicalVolume * getLogical() const noexcept
GBase(const std::shared_ptr< GOptions > &gopt, std::string logger_name="")
std::shared_ptr< GLogger > log
std::vector< double > getReflectivity() const
std::vector< double > getSpecularSpike() const
std::vector< double > getEfficiency() const
bool hasSigmaAlpha() const
std::string getName() const
std::vector< double > getPhotonEnergy() const
std::string getMatOptProps() const
std::vector< double > getBackscatter() const
std::vector< double > getTransmittance() const
std::string getDescription() const
double getSigmaAlpha() const
std::string getType() const
std::vector< double > getSpecularLobe() const
std::string getFinish() const
std::string getBorder() const
std::string getSystem() const
std::string getModel() const
std::vector< double > getIndexOfRefraction() const
GTree(const std::shared_ptr< GOptions > &gopt, std::unordered_map< std::string, G4Volume * > g4volumes_map, std::unordered_map< std::string, const GVolume * > gvolumes_map={}, QWidget *parent=nullptr, std::unordered_map< std::string, const GMirror * > gmirrors_map={})
Construct the geometry tree widget.
Definition gtree.cc:414
std::string getDescription() const
std::string getRot() const
const std::optional< std::string > & getSolidsOpr() const
const std::optional< std::string > & getParameters() const
std::string getPos() const
std::string getType() const
const std::optional< std::string > & getMirror() const
std::string getMotherName() const
#define SFUNCTION_NAME
NORMAL
Option-set definition entry point for the GTree module.
constexpr const char * GTREE_LOGGER
constexpr char MOTHEROFUSALL[]
constexpr char ROOTWORLDGVOLUMENAME[]
bool is_unset(std::string_view s)
void apply_uimanager_commands(const std::string &commands)
vector< std::string > getStringVectorFromString(const std::string &input)