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// Qt
6#include <QHeaderView>
7#include <QTextEdit>
8#include <QVBoxLayout>
9#include <QColorDialog>
10#include <QSignalBlocker>
11
12// gtree
13#include "gtree.h"
14#include "gtree_options.h"
15
16// gemc
17#include "gsystemConventions.h"
18
19// geant4
20#include "G4VisAttributes.hh"
21#include "G4Material.hh"
22#include "gtouchable.h"
23#include "gutilities.h"
24#include "G4SystemOfUnits.hh" // for mm, cm, g/cm3, etc.
25
26// Cache a volume's hierarchy, material and visualization attributes for the UI model.
28 auto pvolume = g4volume->getPhysical();
29 auto lvolume = g4volume->getLogical();
30 auto svolume = g4volume->getSolid();
31
32 std::string lname = lvolume->GetName();
33 if (lname != ROOTWORLDGVOLUMENAME) {
34 auto mlvolume = pvolume->GetMotherLogical();
35 mother = mlvolume->GetName();
36 material = lvolume->GetMaterial()->GetName();
37 }
38 else {
39 mother = MOTHEROFUSALL;
40 material = "G4_Galactic";
41 }
42
43 // Read visualization attributes from the logical volume.
44 auto visAttributes = lvolume->GetVisAttributes();
45 auto gcolor = visAttributes->GetColour();
46
47 double red = gcolor.GetRed();
48 double green = gcolor.GetGreen();
49 double blue = gcolor.GetBlue();
50 auto alpha = gcolor.GetAlpha();
51
52 color = QColor::fromRgbF(red, green, blue);
53
54 opacity = alpha;
55
56 is_visible = visAttributes->IsVisible();
57
58 // Store scaled physics quantities for display (g, cm3, g/cm3).
59 mass = lvolume->GetMass(false, true) / (CLHEP::g);
60 volume = svolume->GetCubicVolume() / CLHEP::cm3;
61 density = (mass / volume);
62}
63
64
65// Extract a short display name from a full "system/volume" name.
66std::string G4Ttree_item::vname_from_v4name(std::string v4name) {
67 // return name after '/'
68 return v4name.substr(v4name.find_last_of('/') + 1);
69}
70
71// Extract the system prefix from a full "system/volume" name.
72std::string G4Ttree_item::system_from_v4name(std::string v4name) {
73 // return name before '/'
74 return v4name.substr(0, v4name.find_last_of('/'));
75}
76
77
78// Construct the widget, build the internal model, create the UI, and connect signals.
79GTree::GTree(const std::shared_ptr<GOptions>& gopt,
80 std::unordered_map<std::string, G4Volume*> g4volumes_map,
81 QWidget* parent) :
82 QWidget(parent),
83 GBase(gopt, GTREE_LOGGER) {
84 // Build the internal representation used to populate the UI tree.
85 build_tree(g4volumes_map);
86
87 // create the UI
88 treeWidget = new QTreeWidget(this);
89 treeWidget->setColumnCount(3);
90 QStringList headers;
91 headers << "Visibility" << "Color" << "Name";
92 treeWidget->setHeaderLabels(headers);
93 treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
94 treeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
95 treeWidget->header()->setSectionResizeMode(2, QHeaderView::Stretch);
96 treeWidget->setRootIsDecorated(true);
97 treeWidget->setAlternatingRowColors(true);
98
99 auto* mainLayout = new QHBoxLayout(this);
100 mainLayout->addWidget(treeWidget, /*stretch*/ 3);
101
102 // Right: property panel has ~same width. Increase stretch to increase
103 rightPanel = right_widget();
104 mainLayout->addWidget(rightPanel, /*stretch*/ 3);
105
106 setLayout(mainLayout);
107
108 // populate the tree from g4_systems_tree
109 populateTree();
110
111 // react to checkboxes
112 connect(treeWidget, &QTreeWidget::itemChanged,
113 this, &GTree::onItemChanged);
114
115
116 // react to clicks / selection to update the right panel
117 connect(treeWidget, &QTreeWidget::itemClicked,
118 this, &GTree::onTreeItemClicked);
119
120 connect(treeWidget, &QTreeWidget::currentItemChanged,
121 this, &GTree::onCurrentItemChanged);
122
123 // connect GQTButtonsWidget signal button_pressed to slot changeStyle()
124 connect(styleButtons->buttonsWidget,
125 SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem*)),
126 this, SLOT(changeStyle()));
127
128
129 // connect slider to slot
130 connect(opacitySlider, &QSlider::valueChanged,
131 this, &GTree::onOpacitySliderChanged);
132
133 log->debug(NORMAL, SFUNCTION_NAME, "GTree added");
134}
135
136// Build the Qt tree view from the internal system/volume model.
137void GTree::populateTree() {
138 // for each system
139 for (auto& [systemName, volMap] : g4_systems_tree) {
140 // top-level item for the system
141 auto* systemItem = new QTreeWidgetItem(treeWidget);
142 systemItem->setText(2, QString::fromStdString(systemName));
143 systemItem->setFlags(systemItem->flags() | Qt::ItemIsUserCheckable);
144 systemItem->setCheckState(0, Qt::Checked);
145
146 // We'll build the volume hierarchy inside this system using a lookup map.
147 std::map<std::string, QTreeWidgetItem*> itemLookup;
148
149 // --------------------------------------------------------------------
150 // 1st pass: create items WITHOUT parents, configure text/data/checkbox
151 // --------------------------------------------------------------------
152 for (auto& [volName, vptr] : volMap) {
153 const G4Ttree_item* vitem = vptr.get();
154
155 auto* item = new QTreeWidgetItem; // no parent yet
156 item->setText(2, QString::fromStdString(G4Ttree_item::vname_from_v4name(volName)));
157 item->setData(2, Qt::UserRole, QString::fromStdString(volName)); // store full v4 name
158
159 // checkbox for visibility:
160 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
161 item->setCheckState(0, vitem->get_visibility() ? Qt::Checked : Qt::Unchecked);
162
163 itemLookup[volName] = item;
164 }
165
166 // --------------------------------------------------------------------
167 // 2nd pass: attach items to their parent (system or mother)
168 // --------------------------------------------------------------------
169 for (auto& [volName, vptr] : volMap) {
170 const G4Ttree_item* vitem = vptr.get();
171 auto mother = vitem->get_mother(); // full v4 name of mother
172 auto* thisItem = itemLookup[volName];
173
174 QTreeWidgetItem* parentItem = systemItem;
175
176 // If a mother exists and is present in this system's lookup, attach under it.
177 if (!mother.empty() && mother != "root") {
178 auto itM = itemLookup.find(mother);
179 if (itM != itemLookup.end()) {
180 parentItem = itM->second;
181 }
182 }
183
184 parentItem->addChild(thisItem);
185 }
186
187 // --------------------------------------------------------------------
188 // 3rd pass: create color buttons now that items are in the tree
189 // --------------------------------------------------------------------
190 for (auto& [volName, vptr] : volMap) {
191 const G4Ttree_item* vitem = vptr.get();
192 QTreeWidgetItem* item = itemLookup[volName];
193
194 auto* colorBtn = new QPushButton(treeWidget);
195 QColor c = vitem->get_color();
196 colorBtn->setFixedSize(20, 20);
197 colorBtn->setFlat(true); // no 3D/bevel look
198 colorBtn->setText(QString()); // no text
199
200 colorBtn->setStyleSheet(
201 QString("QPushButton { background-color: %1; border: 1px solid black; }")
202 .arg(c.name())
203 );
204
205 // Store the full volume name as a property so the slot can retrieve it.
206 colorBtn->setProperty("volumeName", QString::fromStdString(volName));
207 connect(colorBtn, &QPushButton::clicked, this, &GTree::onColorButtonClicked);
208
209 treeWidget->setItemWidget(item, 1, colorBtn);
210 }
211 }
212
213 treeWidget->expandAll();
214}
215
216
217// Build the internal system/volume model from the provided volume map.
218void GTree::build_tree(std::unordered_map<std::string, G4Volume*> g4volumes_map) {
219 // loop over map
220 for (auto [name, g4volume] : g4volumes_map) {
221 // skip the Geant4 world / ROOTWORLDGVOLUMENAME
222 // auto lvolume = g4volume->getLogical();
223 //
224 // if (lvolume && lvolume->GetName() == ROOTWORLDGVOLUMENAME) {
225 // log->info(2, "Skipping world volume >", name, "< from tree");
226 // continue;
227 // }
228
229 auto system_name = G4Ttree_item::system_from_v4name(name);
230
231 // ensure the system exists
232 auto& system_tree = g4_systems_tree[system_name];
233 system_tree[name] = std::make_unique<G4Ttree_item>(g4volume);
234
235 log->info(2, "Adding ", name, " to tree, system_name is ", system_name,
236 ", density: ", system_tree[name]->get_density(),
237 "g/cm3, mass: ", system_tree[name]->get_mass(),
238 "g, volume: ", system_tree[name]->get_volume(), "cm3");
239 }
240}
241
242// Apply visibility checkbox changes to the selected item and its direct children.
243void GTree::onItemChanged(QTreeWidgetItem* item, int column) {
244 if (column != 0) return; // we care about visibility column only
245
246 // is this a volume item? (has stored v4 name in UserRole)
247 QVariant v = item->data(2, Qt::UserRole);
248
249 // --------------------------------------------------------------------
250 // SYSTEM item: no UserRole data → propagate to direct daughters
251 // --------------------------------------------------------------------
252 if (!v.isValid()) {
253 bool visible = (item->checkState(0) == Qt::Checked);
254
255 QSignalBlocker blocker(treeWidget); // avoid recursive itemChanged
256
257 const int nChildren = item->childCount();
258 for (int i = 0; i < nChildren; ++i) {
259 QTreeWidgetItem* child = item->child(i);
260 QVariant cv = child->data(2, Qt::UserRole);
261 if (!cv.isValid())
262 continue; // skip non-volume children (shouldn't happen)
263
264 const QString fullName = cv.toString();
265
266 // sync child checkbox
267 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
268
269 // apply to Geant4
270 set_visibility(fullName.toStdString(), visible);
271 }
272
273 return;
274 }
275
276 // --------------------------------------------------------------------
277 // VOLUME item: apply to itself and its direct daughter volumes
278 // --------------------------------------------------------------------
279 const QString fullName = v.toString();
280 bool visible = (item->checkState(0) == Qt::Checked);
281
282 QSignalBlocker blocker(treeWidget); // avoid recursive itemChanged
283
284 // 1) apply to this volume
285 set_visibility(fullName.toStdString(), visible);
286
287 // 2) apply same visibility to its direct daughters
288 const int nChildren = item->childCount();
289 for (int i = 0; i < nChildren; ++i) {
290 QTreeWidgetItem* child = item->child(i);
291 QVariant cv = child->data(2, Qt::UserRole);
292 if (!cv.isValid())
293 continue; // skip if not a volume
294
295 const QString childName = cv.toString();
296
297 // sync child checkbox
298 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
299
300 // apply to Geant4
301 set_visibility(childName.toStdString(), visible);
302 }
303}
304
305
306// Open the color dialog and apply a new RGB color to the selected volume.
307void GTree::onColorButtonClicked() {
308 auto* btn = qobject_cast<QPushButton*>(sender());
309 if (!btn)
310 return;
311
312 const QString volName = btn->property("volumeName").toString();
313 if (volName.isEmpty())
314 return;
315
316 QColor initial = Qt::white;
317
318 QColor c = QColorDialog::getColor(initial, this, tr("Select color"));
319 if (!c.isValid())
320 return;
321
322 // Update button appearance
323 btn->setStyleSheet(
324 QString("QPushButton { background-color: %1; border: 1px solid black; }")
325 .arg(c.name())
326 );
327
328 // tell your model
329 set_color(volName.toStdString(), c);
330}
331
332
333// Send a Geant4 UI command to toggle visibility for a specific volume.
334void GTree::set_visibility(const std::string& volumeName, bool visible) {
335 std::string vis_int = visible ? "1" : "0";
336
337 std::string command = "/vis/geometry/set/visibility " + volumeName + " -1 " + vis_int;
338
339 // World visibility uses a different depth value.
340 if (volumeName == ROOTWORLDGVOLUMENAME) {
341 command = "/vis/geometry/set/visibility " + volumeName + " 0 " + vis_int;
342 }
343
345}
346
347// Send a Geant4 UI command to set RGB color for a specific volume.
348void GTree::set_color(const std::string& volumeName, const QColor& c) {
349 int r, g, b;
350 c.getRgb(&r, &g, &b);
351
352 std::string command = "/vis/geometry/set/colour " + volumeName + " 0 "
353 + std::to_string(r / 255.0) + " "
354 + std::to_string(g / 255.0) + " "
355 + std::to_string(b / 255.0);
356
358}
359
360// Return number of direct children in the Qt tree for the given item.
361int GTree::get_ndaughters(QTreeWidgetItem* item) const {
362 if (!item) return 0;
363 return item->childCount();
364}
365
366// Find the cached model record for a volume by its full name.
367G4Ttree_item* GTree::findTreeItem(const std::string& fullName) {
368 for (const auto& [systemName, volMap] : g4_systems_tree) {
369 auto it = volMap.find(fullName);
370 if (it != volMap.end()) {
371 return it->second.get();
372 }
373 }
374 return nullptr;
375}
376
377
378// Update the right-side panel according to the clicked item.
379void GTree::onTreeItemClicked(QTreeWidgetItem* item, int /*column*/) {
380 if (!bottomPanel)
381 return;
382
383 if (!item) {
384 bottomPanel->setVisible(false);
385 current_volume_name.clear();
386 return;
387 }
388
389 bottomPanel->setVisible(true);
390
391 // Is it a volume? (volume items store the full v4 name in UserRole)
392 QVariant v = item->data(2, Qt::UserRole);
393 bool isVolume = v.isValid();
394
395 // Type label
396 if (isVolume) {
397 typeLabel->setText(QStringLiteral("<b>G4 Volume</b>"));
398 current_volume_name = v.toString().toStdString();
399 }
400 else {
401 typeLabel->setText(QStringLiteral("<b>System</b>"));
402 current_volume_name.clear();
403 }
404
405 // Number of direct daughters
406 int nd = get_ndaughters(item);
407 daughtersLabel->setText(tr("Daughters: %1").arg(nd));
408
409 // Name (column 2 text)
410 QString itemName = item->text(2);
411 nameLabel->setText(tr("Name: %1").arg(itemName));
412
413 // Material / density / mass
414 if (isVolume) {
415 styleButtons->setVisible(true);
416
417 const std::string fullName = v.toString().toStdString();
418 const G4Ttree_item* titem = findTreeItem(fullName);
419
420 if (titem) {
421 materialLabel->setText(
422 tr("Material: %1").arg(QString::fromStdString(titem->get_material()))
423 );
424 auto mass = titem->get_mass();
425 if (mass < 1000) {
426 massLabel->setText(tr("Total Mass: %1 g").arg(mass));
427 }
428 else {
429 massLabel->setText(tr("Total Mass: %1 kg").arg(mass / 1000));
430 }
431 auto volume = titem->get_volume();
432 if (volume < 1000000) {
433 volumeLabel->setText(tr("Volume: %1 cm3").arg(volume));
434 }
435 else {
436 volumeLabel->setText(tr("Volume: %1 m3").arg(volume / 1000000));
437 }
438
439 densityLabel->setText(
440 tr("Average Density: %1 g / cm3").arg(titem->get_density())
441 );
442
443 // Sync the slider position to the cached alpha channel.
444 double op = titem->get_opacity();
445 int sliderVal = static_cast<int>(op * 100.0 + 0.5);
446 {
447 QSignalBlocker blocker(opacitySlider);
448 opacitySlider->setValue(sliderVal);
449 }
450 opacityLabel->setText(QString::number(op, 'f', 2));
451 opacitySlider->setVisible(true);
452 }
453 }
454 else {
455 styleButtons->setVisible(false);
456 opacitySlider->setVisible(false);
457 // Systems don't have a single material etc.
458 materialLabel->setText(tr(""));
459 massLabel->setText(tr(""));
460 volumeLabel->setText(tr(""));
461 densityLabel->setText(tr(""));
462 }
463}
464
465// Keep the right-side panel updated when selection changes via keyboard navigation.
466void GTree::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) {
467 Q_UNUSED(previous);
468 if (!current)
469 return;
470
471 // reset styleButtons
472 styleButtons->reset_buttons();
473
474 // Reuse the same logic as mouse clicks
475 onTreeItemClicked(current, 0);
476}
477
478// Apply a representation command based on the currently selected style button.
479void GTree::changeStyle() {
480
481 // No-op if no volume is selected (system selected or nothing selected).
482 if (current_volume_name.empty())
483 return;
484
485 int button_index = styleButtons->button_pressed();
486
487 std::string command;
488
489 if (button_index == 0) {
490 command = "/vis/geometry/set/forceWireframe " + current_volume_name + " 0 1 ";
491 }
492 else if (button_index == 1) {
493 command = "/vis/geometry/set/forceSolid " + current_volume_name + " 0 1 ";
494 }
495 else if (button_index == 2) {
496 command = "/vis/geometry/set/forceCloud " + current_volume_name + " 0 1 ";
497 }
498 else {
499 // Unknown button index: avoid issuing an empty or malformed UI command.
500 return;
501 }
502
504}
505
506
507// Convert slider position to alpha and apply it to the currently selected volume.
508void GTree::onOpacitySliderChanged(int value) {
509 if (current_volume_name.empty())
510 return; // no volume selected
511
512 double opacity = value / 100.0;
513
514 if (opacityLabel) {
515 opacityLabel->setText(QString::number(opacity, 'f', 2));
516 }
517
518 set_opacity(current_volume_name, opacity);
519}
520
521// Update alpha for a volume while preserving cached RGB components.
522void GTree::set_opacity(const std::string& volumeName, double opacity) {
523
524 // find current color for this volume from our tree model
525 G4Ttree_item* item = findTreeItem(volumeName);
526 if (!item) return;
527
528 QColor c = item->get_color();
529 double r = c.redF();
530 double g = c.greenF();
531 double b = c.blueF();
532
533 // Geant4: r g b alpha
534 std::string command = "/vis/geometry/set/colour " + volumeName + " 0 "
535 + std::to_string(r) + " "
536 + std::to_string(g) + " "
537 + std::to_string(b) + " "
538 + std::to_string(opacity);
539
540 // Keep the model synchronized with the command we are sending.
541 item->set_color(c);
542 item->set_opacity(opacity);
543
545}
Lightweight per-volume record used by GTree to populate the UI.
Definition gtree.h:37
static std::string vname_from_v4name(std::string v4name)
Extract the "leaf" volume name from a full volume name.
Definition gtree.cc:66
double get_opacity() const
Return the cached opacity (alpha) in [0,1].
Definition gtree.h:142
G4Ttree_item(G4Volume *g4volume)
Construct a cached record for a single geometry volume.
Definition gtree.cc:27
double get_density() const
Return the cached density.
Definition gtree.h:151
void set_opacity(double opacity)
Update the cached opacity.
Definition gtree.h:179
double get_volume() const
Return the cached volume.
Definition gtree.h:148
bool get_visibility() const
Return the cached visibility state.
Definition gtree.h:136
double get_mass() const
Return the cached mass.
Definition gtree.h:145
QColor get_color() const
Return the cached RGB color.
Definition gtree.h:133
void set_color(QColor &c)
Update the cached color.
Definition gtree.h:173
static std::string system_from_v4name(std::string v4name)
Extract the system name from a full volume name.
Definition gtree.cc:72
std::string get_material() const
Return the cached material name.
Definition gtree.h:154
std::string get_mother() const
Return the cached mother name.
Definition gtree.h:130
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, QWidget *parent=nullptr)
Construct the geometry tree widget.
Definition gtree.cc:79
#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)