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