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 <QCoreApplication>
15#include <QHeaderView>
16#include <QTextEdit>
17#include <QVBoxLayout>
18#include <QColorDialog>
19#include <QDialog>
20#include <QDialogButtonBox>
21#include <QSignalBlocker>
22#include <QShortcut>
23#include <QStringList>
24#include <QTimer>
25
26// gtree
27#include "gtree.h"
28#include "gtree_options.h"
29
30// gemc
31#include "gsystemConventions.h"
32
33// geant4
34#include "G4VisAttributes.hh"
35#include "G4Material.hh"
36#include "G4VisManager.hh"
37#include "G4VViewer.hh"
38#include "G4VSceneHandler.hh"
39#include "G4VGraphicsSystem.hh"
40#include "G4UImanager.hh"
41#include "gtouchable.h"
42#include "gutilities.h"
43
44namespace {
45
46std::vector<std::string> splitParams(const std::string& s) {
47 std::vector<std::string> result;
48 std::istringstream ss(s);
49 std::string tok;
50 while (std::getline(ss, tok, ',')) {
51 const auto a = tok.find_first_not_of(" \t");
52 const auto b = tok.find_last_not_of(" \t");
53 if (a != std::string::npos)
54 result.push_back(tok.substr(a, b - a + 1));
55 }
56 return result;
57}
58
59using PDesc = std::vector<const char*>;
60
61const PDesc& solidParamDescs(const std::string& solid, std::size_t n) {
62 static const std::unordered_map<std::string, PDesc> kFixed = {
63 {"G4Box", {QT_TRANSLATE_NOOP("GTreeParameters", "dx: half length in x"),
64 QT_TRANSLATE_NOOP("GTreeParameters", "dy: half length in y"),
65 QT_TRANSLATE_NOOP("GTreeParameters", "dz: half length in z")}},
66 {"G4Tubs", {QT_TRANSLATE_NOOP("GTreeParameters", "rin: inner radius"),
67 QT_TRANSLATE_NOOP("GTreeParameters", "rout: outer radius"),
68 QT_TRANSLATE_NOOP("GTreeParameters", "length: half length in z"),
69 QT_TRANSLATE_NOOP("GTreeParameters", "phi start: starting phi angle"),
70 QT_TRANSLATE_NOOP("GTreeParameters", "phi total: total phi angle")}},
71 {"G4Cons", {QT_TRANSLATE_NOOP("GTreeParameters", "rin1: inner radius at -dz"),
72 QT_TRANSLATE_NOOP("GTreeParameters", "rout1: outer radius at -dz"),
73 QT_TRANSLATE_NOOP("GTreeParameters", "rin2: inner radius at +dz"),
74 QT_TRANSLATE_NOOP("GTreeParameters", "rout2: outer radius at +dz"),
75 QT_TRANSLATE_NOOP("GTreeParameters", "length: half length in z"),
76 QT_TRANSLATE_NOOP("GTreeParameters", "phi start: starting phi angle"),
77 QT_TRANSLATE_NOOP("GTreeParameters", "phi total: total phi angle")}},
78 {"G4Trd", {QT_TRANSLATE_NOOP("GTreeParameters", "dx1: half length in x at -dz"),
79 QT_TRANSLATE_NOOP("GTreeParameters", "dx2: half length in x at +dz"),
80 QT_TRANSLATE_NOOP("GTreeParameters", "dy1: half length in y at -dz"),
81 QT_TRANSLATE_NOOP("GTreeParameters", "dy2: half length in y at +dz"),
82 QT_TRANSLATE_NOOP("GTreeParameters", "z: half length in z")}},
83 {"G4Sphere", {QT_TRANSLATE_NOOP("GTreeParameters", "rmin: inner radius"),
84 QT_TRANSLATE_NOOP("GTreeParameters", "rmax: outer radius"),
85 QT_TRANSLATE_NOOP("GTreeParameters", "sphi: starting phi angle"),
86 QT_TRANSLATE_NOOP("GTreeParameters", "dphi: delta phi angle"),
87 QT_TRANSLATE_NOOP("GTreeParameters", "stheta: starting theta angle"),
88 QT_TRANSLATE_NOOP("GTreeParameters", "dtheta: delta theta angle")}},
89 {"G4Paraboloid", {QT_TRANSLATE_NOOP("GTreeParameters", "dz: half length in z"),
90 QT_TRANSLATE_NOOP("GTreeParameters", "r1: radius at -dz"),
91 QT_TRANSLATE_NOOP("GTreeParameters", "r2: radius at +dz")}},
92 {"G4EllipticalTube", {QT_TRANSLATE_NOOP("GTreeParameters", "dx: half length in x"),
93 QT_TRANSLATE_NOOP("GTreeParameters", "dy: half length in y"),
94 QT_TRANSLATE_NOOP("GTreeParameters", "dz: half length in z")}},
95 };
96 static const std::unordered_map<std::size_t, PDesc> kTrap = {
97 {4, {QT_TRANSLATE_NOOP("GTreeParameters", "pz: length along Z"),
98 QT_TRANSLATE_NOOP("GTreeParameters", "py: length along Y"),
99 QT_TRANSLATE_NOOP("GTreeParameters", "px: length along X (wider side)"),
100 QT_TRANSLATE_NOOP("GTreeParameters", "pltx: length along X (narrower side)")}},
101 {11, {QT_TRANSLATE_NOOP("GTreeParameters", "pDz: half Z length"),
102 QT_TRANSLATE_NOOP("GTreeParameters", "pTheta: polar angle of line joining base centres"),
103 QT_TRANSLATE_NOOP("GTreeParameters", "pPhi: azimuthal angle of line joining base centres"),
104 QT_TRANSLATE_NOOP("GTreeParameters", "pDy1: half Y length at -dz"),
105 QT_TRANSLATE_NOOP("GTreeParameters", "pDx1: half X length at smaller Y, base at -dz"),
106 QT_TRANSLATE_NOOP("GTreeParameters", "pDx2: half X length at bigger Y, base at -dz"),
107 QT_TRANSLATE_NOOP("GTreeParameters", "pAlp1: angle between Y-axis and centre line at -dz"),
108 QT_TRANSLATE_NOOP("GTreeParameters", "pDy2: half Y length at +dz"),
109 QT_TRANSLATE_NOOP("GTreeParameters", "pDx3: half X length at smaller Y, base at +dz"),
110 QT_TRANSLATE_NOOP("GTreeParameters", "pDx4: half X length at bigger Y, base at +dz"),
111 QT_TRANSLATE_NOOP("GTreeParameters", "pAlp2: angle between Y-axis and centre line at +dz")}},
112 };
113 static const PDesc kEmpty;
114
115 if (solid == "G4Trap") {
116 const auto it = kTrap.find(n);
117 return it != kTrap.end() ? it->second : kEmpty;
118 }
119 const auto it = kFixed.find(solid);
120 return it != kFixed.end() ? it->second : kEmpty;
121}
122
123// Format a "number*unit" or bare "number" token: max 3 decimal places,
124// values with |x| < 1e-7 are shown as 0.
125static QString formatVal(const std::string& token) {
126 const auto star = token.find('*');
127 const std::string numPart = (star == std::string::npos) ? token : token.substr(0, star);
128 const std::string unit = (star == std::string::npos) ? "" : token.substr(star);
129 try {
130 std::size_t pos = 0;
131 double v = std::stod(numPart, &pos);
132 if (pos != numPart.size()) return QString::fromStdString(token);
133 if (std::abs(v) < 1e-7) v = 0.0;
134 QString s = QString::number(v, 'f', 3);
135 while (s.endsWith('0') && s.contains('.')) s.chop(1);
136 if (s.endsWith('.')) s.chop(1);
137 return s + QString::fromStdString(unit);
138 } catch (...) {
139 return QString::fromStdString(token);
140 }
141}
142
143QString formatParameters(const std::string& solid, const std::string& paramsStr) {
144 if (paramsStr.empty()) return {};
145 const auto vals = splitParams(paramsStr);
146 if (vals.empty()) return {};
147 QString html = QCoreApplication::translate("GTreeParameters", "Parameters:");
148 // Polycone: fixed header (phiStart, phiTotal, nplanes) then arrays
149 if (solid == "G4Polycone" && vals.size() >= 3) {
150 const char* kPcFixed[3] = {
151 QT_TRANSLATE_NOOP("GTreeParameters", "phi start: starting phi angle"),
152 QT_TRANSLATE_NOOP("GTreeParameters", "phi total: total phi angle"),
153 QT_TRANSLATE_NOOP("GTreeParameters", "nplanes: number of planes")
154 };
155 for (int i = 0; i < 3 && i < (int)vals.size(); ++i)
156 html += QString(QT_TRANSLATE_NOOP("GTreeParameters", "<br>&nbsp;&nbsp;<i>%1</i>: %2"))
157 .arg(QCoreApplication::translate("GTreeParameters", 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 ? QCoreApplication::translate("GTreeParameters", descs[i])
185 : QCoreApplication::translate("GTreeParameters", "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.contains(operand)) return operand;
195
196 const std::string qualified = systemName.empty() ? operand : systemName + "/" + operand;
197 if (gvolumes.contains(qualified)) 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 << tr("Visibility") << tr("Color") << tr("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 treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
438
439 auto* mainLayout = new QHBoxLayout(this);
440 mainLayout->addWidget(treeWidget, /*stretch*/ 3);
441
442 // Right: property panel has ~same width. Increase stretch to increase
443 rightPanel = right_widget();
444 mainLayout->addWidget(rightPanel, /*stretch*/ 3);
445
446 setLayout(mainLayout);
447
448 // populate the tree from g4_systems_tree
449 populateTree();
450
451 // react to checkboxes
452 connect(treeWidget, &QTreeWidget::itemChanged,
453 this, &GTree::onItemChanged);
454
455 // Toggle the selected group from any column while the tree has keyboard focus.
456 auto* visibilityShortcut = new QShortcut(QKeySequence(Qt::Key_Space), treeWidget);
457 visibilityShortcut->setContext(Qt::WidgetShortcut);
458 visibilityShortcut->setAutoRepeat(false);
459 connect(visibilityShortcut, &QShortcut::activated, this, [this] {
460 const auto items = treeWidget->selectedItems();
461 bool anyVisible = false;
462 for (const auto* item : items) {
463 if (item->checkState(0) == Qt::Checked) anyVisible = true;
464 }
465 const auto state = anyVisible ? Qt::Unchecked : Qt::Checked;
466 for (auto* item : items) {
467 if ((item->flags() & Qt::ItemIsUserCheckable) && !item->isDisabled()) {
468 item->setCheckState(0, state);
469 }
470 }
471 });
472
473
474 // react to clicks / selection to update the right panel
475 connect(treeWidget, &QTreeWidget::itemClicked,
476 this, &GTree::onTreeItemClicked);
477
478 connect(treeWidget, &QTreeWidget::currentItemChanged,
479 this, &GTree::onCurrentItemChanged);
480
481 // connect GQTButtonsWidget signal button_pressed to slot changeStyle()
482 connect(styleButtons->buttonsWidget,
483 SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem*)),
484 this, SLOT(changeStyle()));
485
486
487 // connect slider to slot
488 connect(opacitySlider, &QSlider::valueChanged,
489 this, &GTree::onOpacitySliderChanged);
490
491
492 log->debug(NORMAL, "%1GTree added", SFUNCTION_NAME);
493}
494
495// Build the Qt tree view from the internal system/volume model.
496void GTree::populateTree() {
497 // for each system
498 for (auto& [systemName, volMap] : g4_systems_tree) {
499 // top-level item for the system
500 auto* systemItem = new QTreeWidgetItem(treeWidget);
501 systemItem->setText(2, QString::fromStdString(systemName));
502 systemItem->setFlags(systemItem->flags() | Qt::ItemIsUserCheckable);
503 systemItem->setCheckState(0, Qt::Checked);
504
505 // We'll build the volume hierarchy inside this system using a lookup map.
506 std::map<std::string, QTreeWidgetItem*> itemLookup;
507
508 // --------------------------------------------------------------------
509 // 1st pass: create items WITHOUT parents, configure text/data/checkbox
510 // --------------------------------------------------------------------
511 for (auto& [volName, vptr] : volMap) {
512 const G4Ttree_item* vitem = vptr.get();
513
514 auto* item = new QTreeWidgetItem; // no parent yet
515 item->setText(2, QString::fromStdString(G4Ttree_item::vname_from_v4name(volName)));
516 item->setData(2, Qt::UserRole, QString::fromStdString(volName)); // store full v4 name
517
518 // checkbox for visibility:
519 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
520 item->setCheckState(0, vitem->get_visibility() ? Qt::Checked : Qt::Unchecked);
521
522 itemLookup[volName] = item;
523 }
524
525 // --------------------------------------------------------------------
526 // 2nd pass: attach items to their parent (system or mother)
527 // --------------------------------------------------------------------
528 for (auto& [volName, vptr] : volMap) {
529 const G4Ttree_item* vitem = vptr.get();
530 auto mother = vitem->get_mother(); // full v4 name of mother
531 auto* thisItem = itemLookup[volName];
532
533 QTreeWidgetItem* parentItem = systemItem;
534
535 // If a mother exists and is present in this system's lookup, attach under it.
536 if (!mother.empty() && mother != "root") {
537 auto itM = itemLookup.find(mother);
538 if (itM != itemLookup.end()) {
539 parentItem = itM->second;
540 }
541 }
542
543 parentItem->addChild(thisItem);
544 }
545
546 // --------------------------------------------------------------------
547 // 3rd pass: create color buttons now that items are in the tree
548 // --------------------------------------------------------------------
549 for (auto& [volName, vptr] : volMap) {
550 const G4Ttree_item* vitem = vptr.get();
551 QTreeWidgetItem* item = itemLookup[volName];
552
553 auto* colorBtn = new QPushButton(treeWidget);
554 QColor c = vitem->get_color();
555 colorBtn->setFixedSize(20, 20);
556 colorBtn->setFlat(true); // no 3D/bevel look
557 colorBtn->setText(QString()); // no text
558
559 colorBtn->setStyleSheet(
560 QString("QPushButton { background-color: %1; border: 1px solid %2; }")
561 .arg(c.name(), palette().color(QPalette::Mid).name())
562 );
563
564 // Store the full volume name as a property so the slot can retrieve it.
565 colorBtn->setProperty("volumeName", QString::fromStdString(volName));
566 connect(colorBtn, &QPushButton::clicked, this, &GTree::onColorButtonClicked);
567
568 treeWidget->setItemWidget(item, 1, colorBtn);
569 }
570 }
571
572 treeWidget->expandAll();
573}
574
575
576// Build the internal system/volume model from the provided volume map.
577void GTree::build_tree(std::unordered_map<std::string, G4Volume*> g4volumes_map) {
578 // loop over map
579 for (auto [name, g4volume] : g4volumes_map) {
580 if (g4volume == nullptr || g4volume->getLogical() == nullptr ||
581 g4volume->getSolid() == nullptr || g4volume->getPhysical() == nullptr) {
582 log->info(2, "Skipping non-placed helper volume <%1> from tree", name);
583 continue;
584 }
585
586 // skip the Geant4 world / gsystem::ROOTWORLDGVOLUMENAME
587 // auto lvolume = g4volume->getLogical();
588 //
589 // if (lvolume && lvolume->GetName() == gsystem::ROOTWORLDGVOLUMENAME) {
590 // log->info(2, "Skipping world volume >", name, "< from tree");
591 // continue;
592 // }
593
594 auto system_name = G4Ttree_item::system_from_v4name(name);
595
596 // ensure the system exists
597 auto& system_tree = g4_systems_tree[system_name];
598 const GVolume* gvol = nullptr;
599 auto it = gvolumes_map.find(name);
600 if (it != gvolumes_map.end()) gvol = it->second;
601 system_tree[name] = std::make_unique<G4Ttree_item>(g4volume, gvol);
602
603 // do not log mass/volume/density here: reading them triggers the (potentially
604 // Monte-Carlo) physics computation for every volume in the tree
605 log->info(2, "Adding %1 to tree, system_name is %2", name, system_name);
606 }
607}
608
609// Apply visibility checkbox changes to the selected item and its direct children.
610void GTree::onItemChanged(QTreeWidgetItem* item, int column) {
611 if (column != 0) return; // we care about visibility column only
612
613 // is this a volume item? (has stored v4 name in UserRole)
614 QVariant v = item->data(2, Qt::UserRole);
615
616 // --------------------------------------------------------------------
617 // SYSTEM item: no UserRole data → propagate to direct daughters
618 // --------------------------------------------------------------------
619 if (!v.isValid()) {
620 bool visible = (item->checkState(0) == Qt::Checked);
621
622 QSignalBlocker blocker(treeWidget); // avoid recursive itemChanged
623
624 const int nChildren = item->childCount();
625 for (int i = 0; i < nChildren; ++i) {
626 QTreeWidgetItem* child = item->child(i);
627 QVariant cv = child->data(2, Qt::UserRole);
628 if (!cv.isValid())
629 continue; // skip non-volume children (shouldn't happen)
630
631 const QString fullName = cv.toString();
632
633 // sync child checkbox
634 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
635
636 // apply to Geant4
637 set_visibility(fullName.toStdString(), visible);
638 }
639
640 return;
641 }
642
643 // --------------------------------------------------------------------
644 // VOLUME item: apply to itself and its direct daughter volumes
645 // --------------------------------------------------------------------
646 const QString fullName = v.toString();
647 bool visible = (item->checkState(0) == Qt::Checked);
648
649 QSignalBlocker blocker(treeWidget); // avoid recursive itemChanged
650
651 // 1) apply to this volume
652 set_visibility(fullName.toStdString(), visible);
653
654 // 2) apply same visibility to its direct daughters
655 const int nChildren = item->childCount();
656 for (int i = 0; i < nChildren; ++i) {
657 QTreeWidgetItem* child = item->child(i);
658 QVariant cv = child->data(2, Qt::UserRole);
659 if (!cv.isValid())
660 continue; // skip if not a volume
661
662 const QString childName = cv.toString();
663
664 // sync child checkbox
665 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
666
667 // apply to Geant4
668 set_visibility(childName.toStdString(), visible);
669 }
670}
671
672
673// Open the color dialog and apply a new RGB color to the selected volume.
674void GTree::onColorButtonClicked() {
675 auto* btn = qobject_cast<QPushButton*>(sender());
676 if (!btn)
677 return;
678
679 const QString volName = btn->property("volumeName").toString();
680 if (volName.isEmpty())
681 return;
682
683 QColor initial = Qt::white;
684
685 QColor c = QColorDialog::getColor(initial, this, tr("Select color"));
686 if (!c.isValid())
687 return;
688
689 // Update button appearance
690 btn->setStyleSheet(
691 QString("QPushButton { background-color: %1; border: 1px solid %2; }")
692 .arg(c.name(), palette().color(QPalette::Mid).name())
693 );
694
695 // tell your model
696 set_color(volName.toStdString(), c);
697}
698
699
700// Send a Geant4 UI command to toggle visibility for a specific volume.
701void GTree::set_visibility(const std::string& volumeName, bool visible) {
702 std::string vis_int = visible ? "1" : "0";
703
704 std::string command = "/vis/geometry/set/visibility " + volumeName + " -1 " + vis_int;
705
706 // World visibility uses a different depth value.
707 if (volumeName == gsystem::ROOTWORLDGVOLUMENAME) {
708 command = "/vis/geometry/set/visibility " + volumeName + " 0 " + vis_int;
709 }
710
712}
713
714// Send a Geant4 UI command to set RGB color for a specific volume.
715void GTree::set_color(const std::string& volumeName, const QColor& c) {
716 G4Ttree_item* item = findTreeItem(volumeName);
717 double currentOpacity = item ? item->get_opacity() : 1.0;
718 if (item) item->set_color(c);
719
720 std::string command = "/vis/geometry/set/colour " + volumeName + " 0 "
721 + std::to_string(c.redF()) + " "
722 + std::to_string(c.greenF()) + " "
723 + std::to_string(c.blueF()) + " "
724 + std::to_string(currentOpacity);
725
727}
728
729// Return number of direct children in the Qt tree for the given item.
730int GTree::get_ndaughters(QTreeWidgetItem* item) const {
731 if (!item) return 0;
732 return item->childCount();
733}
734
735// Find the cached model record for a volume by its full name.
736G4Ttree_item* GTree::findTreeItem(const std::string& fullName) {
737 for (const auto& [systemName, volMap] : g4_systems_tree) {
738 auto it = volMap.find(fullName);
739 if (it != volMap.end()) {
740 return it->second.get();
741 }
742 }
743 return nullptr;
744}
745
746
747// Update the right-side panel according to the clicked item.
748void GTree::onTreeItemClicked(QTreeWidgetItem* item, int /*column*/) {
749 if (!bottomPanel)
750 return;
751
752 if (!item) {
753 bottomPanel->setVisible(false);
754 current_volume_name.clear();
755 return;
756 }
757
758 bottomPanel->setVisible(true);
759
760 // Is it a volume? (volume items store the full v4 name in UserRole)
761 QVariant v = item->data(2, Qt::UserRole);
762 bool isVolume = v.isValid();
763
764 // Type label
765 if (isVolume) {
766 typeLabel->setText(tr("<b>G4 Volume</b>"));
767 current_volume_name = v.toString().toStdString();
768 }
769 else {
770 typeLabel->setText(tr("<b>System</b>"));
771 current_volume_name.clear();
772 }
773
774 // Number of direct daughters
775 int nd = get_ndaughters(item);
776 daughtersLabel->setText(tr("Daughters: %1").arg(nd));
777
778 // Name (column 2 text)
779 QString itemName = item->text(2);
780 nameLabel->setText(tr("Name: %1").arg(itemName));
781
782 // Material / density / mass
783 if (isVolume) {
784 styleButtons->setVisible(true);
785 if (mirrorButton) mirrorButton->setVisible(false);
786
787 const std::string fullName = v.toString().toStdString();
788 const G4Ttree_item* titem = findTreeItem(fullName);
789
790 if (titem) {
791 materialLabel->setText(
792 tr("Material: %1").arg(QString::fromStdString(titem->get_material()))
793 );
794 auto mass = titem->get_mass();
795 if (mass < 1000) {
796 massLabel->setText(tr("Mass: %1 g").arg(mass));
797 }
798 else {
799 massLabel->setText(tr("Mass: %1 kg").arg(mass / 1000));
800 }
801 auto volume = titem->get_volume();
802 if (volume < 1000000) {
803 volumeLabel->setText(tr("Volume: %1 cm3").arg(volume));
804 }
805 else {
806 volumeLabel->setText(tr("Volume: %1 m3").arg(volume / 1000000));
807 }
808
809 densityLabel->setText(
810 tr("Density: %1 g / cm3").arg(titem->get_density())
811 );
812
813 // pygemc descriptor fields
814 const auto solidT = titem->get_solidType();
815 solidTypeLabel->setText(solidT.empty() ? QString()
816 : tr("Solid: %1").arg(QString::fromStdString(solidT)));
817 const auto params = titem->get_parameters();
818 const bool hasParams = !params.empty();
819 parametersLabel->setVisible(hasParams);
820 if (hasParams) parametersLabel->setHtml(formatParameters(solidT, params));
821 const auto pos = titem->get_position();
822 positionLabel->setText(pos.empty() ? QString()
823 : tr("Position: %1").arg(QString::fromStdString(pos)));
824 const auto rot = titem->get_rotation();
825 rotationLabel->setText(rot.empty() ? QString()
826 : tr("Rotation: %1").arg(QString::fromStdString(rot)));
827 const auto mom = titem->get_motherVolume();
828 motherLabel->setText(mom.empty() ? QString() : tr("Mother: %1").arg(QString::fromStdString(mom)));
829 const auto boolDesc =
830 formatBooleanOperationDescription(fullName, titem->get_solidsOpr(), gvolumes_map);
831 if (!boolDesc.isEmpty()) {
832 descriptionLabel->setText(boolDesc);
833 }
834 else {
835 const auto desc = titem->get_volDescription();
836 descriptionLabel->setText(desc.empty() ? QString()
837 : tr("Description: %1")
838 .arg(QString::fromStdString(desc)));
839 }
840
841 const auto mirrorName = titem->get_mirrorName();
842 const bool hasMirror = !gutilities::is_unset(mirrorName) && mirrorName != "no" &&
843 mirrorName != "none";
844 if (mirrorButton && hasMirror) {
845 const auto slash = fullName.find_last_of('/');
846 const std::string systemName = slash == std::string::npos ? "" : fullName.substr(0, slash);
847 const std::string mirrorKey = systemName + "/" + mirrorName;
848 if (gmirrors_map.contains(mirrorKey)) {
849 mirrorButton->setText(tr("Mirror: %1").arg(QString::fromStdString(mirrorName)));
850 mirrorButton->setProperty("mirrorKey", QString::fromStdString(mirrorKey));
851 mirrorButton->setVisible(true);
852 }
853 }
854
855 // Sync the slider position to the cached alpha channel.
856 double op = titem->get_opacity();
857 int sliderVal = static_cast<int>(op * 100.0 + 0.5);
858 {
859 QSignalBlocker blocker(opacitySlider);
860 opacitySlider->setValue(sliderVal);
861 }
862 opacityLabel->setText(QString::number(op, 'f', 2));
863 opacitySlider->setVisible(true);
864 }
865
866 // Update the inspect and draw-overlaps buttons with the selected volume's leaf name.
867 if (inspectButton) {
868 const QString leaf = QString::fromStdString(G4Ttree_item::vname_from_v4name(current_volume_name));
869 inspectButton->setText(tr("Inspect %1").arg(leaf));
870 inspectButton->setVisible(true);
871 }
872 if (drawOverlapsButton) {
873 const QString leaf = QString::fromStdString(G4Ttree_item::vname_from_v4name(current_volume_name));
874 drawOverlapsButton->setText(tr("Draw Logical Overlap within %1").arg(leaf));
875 drawOverlapsButton->setVisible(true);
876 }
877 }
878 else {
879 styleButtons->setVisible(false);
880 opacitySlider->setVisible(false);
881 if (mirrorButton) mirrorButton->setVisible(false);
882 if (inspectButton) inspectButton->setVisible(false);
883 if (drawOverlapsButton) drawOverlapsButton->setVisible(false);
884 // Systems don't have a single material etc.
885 materialLabel->setText(QString());
886 massLabel->setText(QString());
887 volumeLabel->setText(QString());
888 densityLabel->setText(QString());
889 solidTypeLabel->setText(QString());
890 parametersLabel->clear();
891 parametersLabel->setVisible(false);
892 positionLabel->setText(QString());
893 rotationLabel->setText(QString());
894 motherLabel->setText(QString());
895 descriptionLabel->setText(QString());
896 }
897}
898
899// Keep the right-side panel updated when selection changes via keyboard navigation.
900void GTree::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) {
901 Q_UNUSED(previous);
902 if (!current)
903 return;
904
905 // reset styleButtons
906 styleButtons->reset_buttons();
907
908 // Reuse the same logic as mouse clicks
909 onTreeItemClicked(current, 0);
910}
911
912// Apply a representation command based on the currently selected style button.
913void GTree::changeStyle() {
914
915 // No-op if no volume is selected (system selected or nothing selected).
916 if (current_volume_name.empty())
917 return;
918
919 int button_index = styleButtons->button_pressed();
920
921 if (button_index == 3) {
922 // Action button: deselect it immediately (it is not a persistent style toggle).
923 {
924 QSignalBlocker blocker(styleButtons->buttonsWidget);
925 styleButtons->reset_buttons();
926 }
927 centreTwinkle();
928 return;
929 }
930
931 std::string command;
932
933 if (button_index == 0) {
934 command = "/vis/geometry/set/forceWireframe " + current_volume_name + " 0 1 ";
935 }
936 else if (button_index == 1) {
937 command = "/vis/geometry/set/forceSolid " + current_volume_name + " 0 1 ";
938 }
939 else if (button_index == 2) {
940 command = "/vis/geometry/set/forceCloud " + current_volume_name + " 0 1 ";
941 }
942 else {
943 // Unknown button index: avoid issuing an empty or malformed UI command.
944 return;
945 }
946
948}
949
950
951// Centre the viewer on the selected volume and kick off the twinkle animation.
952void GTree::centreTwinkle() {
953 G4Ttree_item* item = findTreeItem(current_volume_name);
954 if (!item) return;
955
956 twinkleVolumeName = current_volume_name;
957 twinkleSavedColor = item->get_color();
958 twinkleSavedOpacity = item->get_opacity();
959 twinkleTick = 0;
960
961 gutilities::apply_uimanager_commands("/vis/viewer/centreOn " + twinkleVolumeName);
962
963 if (!twinkleTimer) {
964 twinkleTimer = new QTimer(this);
965 connect(twinkleTimer, &QTimer::timeout, this, &GTree::onTwinkleStep);
966 }
967 if (twinkleTimer->isActive())
968 twinkleTimer->stop();
969
970 twinkleTimer->start(180);
971}
972
973
974// Cycle through flash colors, then restore the original colour and alpha.
975void GTree::onTwinkleStep() {
976 constexpr int kSteps = 5;
977 static const QColor kFlash[kSteps] = {
978 QColor(255, 50, 50), // red
979 QColor(255, 220, 0), // yellow
980 QColor( 50, 255, 50), // green
981 QColor( 0, 220, 255), // cyan
982 QColor(220, 50, 255), // magenta
983 };
984
985 if (twinkleTick < kSteps) {
986 set_color(twinkleVolumeName, kFlash[twinkleTick]);
987 ++twinkleTick;
988 } else {
989 twinkleTimer->stop();
990 G4Ttree_item* item = findTreeItem(twinkleVolumeName);
991 if (item) {
992 item->set_color(twinkleSavedColor);
993 item->set_opacity(twinkleSavedOpacity);
994 }
995 const double r = twinkleSavedColor.redF();
996 const double g = twinkleSavedColor.greenF();
997 const double b = twinkleSavedColor.blueF();
998 const std::string cmd = "/vis/geometry/set/colour " + twinkleVolumeName + " 0 "
999 + std::to_string(r) + " "
1000 + std::to_string(g) + " "
1001 + std::to_string(b) + " "
1002 + std::to_string(twinkleSavedOpacity);
1004 }
1005}
1006
1007
1008// Open the selected volume in a new viewer window of the same driver type.
1009void GTree::inspectVolume() {
1010 drawVolume(false);
1011}
1012
1013// Open the selected logical volume with Geant4's overlap visualization enabled.
1014void GTree::drawOverlaps() {
1015 drawVolume(true);
1016}
1017
1018// Share viewer setup and focus restoration between inspection and overlap visualization.
1019void GTree::drawVolume(bool overlaps) {
1020 if (current_volume_name.empty()) return;
1021
1022 G4UImanager* uim = G4UImanager::GetUIpointer();
1023 if (!uim) return;
1024
1025 // Resolve the active driver and save the viewer name so we can return focus
1026 // to the original window after opening the inspect window.
1027 std::string driverName = "OGLSQt";
1028 std::string originalViewerName;
1029 auto* vm = G4VisManager::GetInstance();
1030 if (vm) {
1031 const auto* viewer = vm->GetCurrentViewer();
1032 if (viewer) {
1033 originalViewerName = viewer->GetName();
1034 const auto* sh = viewer->GetSceneHandler();
1035 if (sh) {
1036 const auto* gs = sh->GetGraphicsSystem();
1037 if (gs) {
1038 driverName = gs->GetNickname();
1039 // ToolsSG cannot render the standalone placement used by logical-volume models.
1040 if (overlaps && gs->GetName().starts_with("TOOLSSG")) driverName = "OGLSQt";
1041 }
1042 }
1043 }
1044 }
1045
1046 const std::string leafName = G4Ttree_item::vname_from_v4name(current_volume_name);
1047
1048 // Open a new window, populate it with only the target volume, and flush.
1049 // /vis/open creates a new viewer whose scene handler attaches to the current (full) scene.
1050 // /vis/scene/create makes a new empty scene but does NOT re-attach the handler.
1051 // /vis/sceneHandler/attach must come before /vis/scene/add/volume to wire the new handler
1052 // to the new empty scene; otherwise the first flush renders the full detector.
1053 if (uim->ApplyCommand("/vis/open " + driverName) != 0) return;
1054 uim->ApplyCommand("/vis/scene/create");
1055 uim->ApplyCommand("/vis/sceneHandler/attach");
1056 if (overlaps) {
1057 uim->ApplyCommand("/vis/viewer/set/style wireframe");
1058 uim->ApplyCommand("/vis/viewer/set/hiddenMarker false");
1059 uim->ApplyCommand("/vis/scene/add/logicalVolume " + current_volume_name);
1060 }
1061 else {
1062 uim->ApplyCommand("/vis/scene/add/volume " + current_volume_name + " -1");
1063 }
1064 uim->ApplyCommand("/vis/viewer/set/background 1 1 1 1");
1065 uim->ApplyCommand("/vis/viewer/set/lineSegmentsPerCircle 100");
1066 // 2D label: large black text centred near the top of the window.
1067 // "! !" tells Geant4 to use the colour and layout set by /vis/set/text*.
1068 uim->ApplyCommand("/vis/set/textColour black");
1069 uim->ApplyCommand("/vis/set/textLayout centre");
1070 uim->ApplyCommand("/vis/scene/add/text2D 0 0.85 36 ! ! " + leafName);
1071 uim->ApplyCommand("/vis/viewer/flush");
1072
1073 // Return Geant4's "current viewer" to the original window so all GUI controls
1074 // (camera, scene properties, tree widget) continue to act on the main view.
1075 if (!originalViewerName.empty())
1076 uim->ApplyCommand("/vis/viewer/select " + originalViewerName);
1077}
1078
1079// Display all properties of the mirror associated with the selected volume.
1080void GTree::showMirrorProperties() {
1081 if (!mirrorButton) return;
1082
1083 const std::string mirrorKey = mirrorButton->property("mirrorKey").toString().toStdString();
1084 const auto mirrorIt = gmirrors_map.find(mirrorKey);
1085 if (mirrorIt == gmirrors_map.end() || mirrorIt->second == nullptr) return;
1086
1087 const GMirror& mirror = *mirrorIt->second;
1088 QDialog dialog(this);
1089 dialog.setWindowTitle(tr("Mirror: %1").arg(QString::fromStdString(mirror.getName())));
1090 dialog.setModal(true);
1091
1092 auto* layout = new QVBoxLayout(&dialog);
1093 auto* properties = new QTextEdit(&dialog);
1094 properties->setReadOnly(true);
1095 properties->setHtml(formatMirrorProperties(mirror));
1096 layout->addWidget(properties);
1097
1098 auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
1099 connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
1100 layout->addWidget(buttons);
1101
1102 dialog.resize(650, 520);
1103 dialog.exec();
1104}
1105
1106
1107// Convert slider position to alpha and apply it to the currently selected volume.
1108void GTree::onOpacitySliderChanged(int value) {
1109 if (current_volume_name.empty())
1110 return; // no volume selected
1111
1112 double opacity = value / 100.0;
1113
1114 if (opacityLabel) {
1115 opacityLabel->setText(QString::number(opacity, 'f', 2));
1116 }
1117
1118 set_opacity(current_volume_name, opacity);
1119}
1120
1121// Update alpha for a volume while preserving cached RGB components.
1122void GTree::set_opacity(const std::string& volumeName, double opacity) {
1123
1124 // find current color for this volume from our tree model
1125 G4Ttree_item* item = findTreeItem(volumeName);
1126 if (!item) return;
1127
1128 QColor c = item->get_color();
1129 double r = c.redF();
1130 double g = c.greenF();
1131 double b = c.blueF();
1132
1133 // Geant4: r g b alpha
1134 std::string command = "/vis/geometry/set/colour " + volumeName + " 0 "
1135 + std::to_string(r) + " "
1136 + std::to_string(g) + " "
1137 + std::to_string(b) + " "
1138 + std::to_string(opacity);
1139
1140 // Keep the model synchronized with the command we are sending.
1141 item->set_color(c);
1142 item->set_opacity(opacity);
1143
1145}
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)