13#include <QColorDialog>
15#include <QSignalBlocker>
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"
38std::vector<std::string> splitParams(
const std::string& s) {
39 std::vector<std::string> result;
40 std::istringstream ss(s);
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));
51using PDesc = std::vector<const char*>;
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",
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",
77 "sphi: starting phi angle",
78 "dphi: delta phi angle",
79 "stheta: starting theta angle",
80 "dtheta: delta theta angle"}},
82 static const std::unordered_map<std::size_t, PDesc> kTrap = {
83 {4, {
"pz: length along Z",
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"}},
99 static const PDesc kEmpty;
101 if (solid ==
"G4Trap") {
102 const auto it = kTrap.find(n);
103 return it != kTrap.end() ? it->second : kEmpty;
105 const auto it = kFixed.find(solid);
106 return it != kFixed.end() ? it->second : kEmpty;
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);
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);
125 return QString::fromStdString(token);
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 {};
134 QString html =
"Parameters:";
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"
143 for (
int i = 0; i < 3 && i < (int)vals.size(); ++i)
144 html += QString(
"<br> <i>%1</i>: %2").arg(kPcFixed[i], formatVal(vals[i]));
146 try { nplanes = std::stoi(vals[2]); }
catch (...) {}
148 for (
int p = 0; p < nplanes && idx < (int)vals.size(); ++p, ++idx)
149 html += QString(
"<br> <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> <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> <i>rout[%1]</i>: %2").arg(p).arg(formatVal(vals[idx]));
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> <i>v%1%2</i>: %3")
163 .arg(v + 1).arg(kAxes[c]).arg(formatVal(vals[v * 3 + c]));
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> <i>%1</i>: %2").arg(desc, formatVal(vals[i]));
185 auto svolume = g4volume->
getSolid();
187 std::string lname = lvolume->GetName();
189 auto mlvolume = pvolume->GetMotherLogical();
190 mother = mlvolume->GetName();
191 material = lvolume->GetMaterial()->GetName();
195 material =
"G4_Galactic";
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();
208 color = QColor::fromRgbF(1.0, 1.0, 1.0);
214 mass = lvolume->GetMass(
false,
true) / (CLHEP::g);
215 volume = svolume->GetCubicVolume() / CLHEP::cm3;
216 density = (mass / volume);
219 solidType = gvolume->
getType();
221 position = gvolume->
getPos();
222 rotation = gvolume->
getRot();
232 return v4name.substr(v4name.find_last_of(
'/') + 1);
238 return v4name.substr(0, v4name.find_last_of(
'/'));
244 std::unordered_map<std::string, G4Volume*> g4volumes_map,
245 std::unordered_map<std::string, const GVolume*> gvolumes_map_in,
249 gvolumes_map(std::move(gvolumes_map_in)) {
251 build_tree(g4volumes_map);
254 treeWidget =
new QTreeWidget(
this);
255 treeWidget->setColumnCount(3);
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);
265 auto* mainLayout =
new QHBoxLayout(
this);
266 mainLayout->addWidget(treeWidget, 3);
269 rightPanel = right_widget();
270 mainLayout->addWidget(rightPanel, 3);
272 setLayout(mainLayout);
278 connect(treeWidget, &QTreeWidget::itemChanged,
279 this, >ree::onItemChanged);
283 connect(treeWidget, &QTreeWidget::itemClicked,
284 this, >ree::onTreeItemClicked);
286 connect(treeWidget, &QTreeWidget::currentItemChanged,
287 this, >ree::onCurrentItemChanged);
291 SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem*)),
292 this, SLOT(changeStyle()));
296 connect(opacitySlider, &QSlider::valueChanged,
297 this, >ree::onOpacitySliderChanged);
304void GTree::populateTree() {
306 for (
auto& [systemName, volMap] : g4_systems_tree) {
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);
314 std::map<std::string, QTreeWidgetItem*> itemLookup;
319 for (
auto& [volName, vptr] : volMap) {
322 auto* item =
new QTreeWidgetItem;
324 item->setData(2, Qt::UserRole, QString::fromStdString(volName));
327 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
328 item->setCheckState(0, vitem->
get_visibility() ? Qt::Checked : Qt::Unchecked);
330 itemLookup[volName] = item;
336 for (
auto& [volName, vptr] : volMap) {
339 auto* thisItem = itemLookup[volName];
341 QTreeWidgetItem* parentItem = systemItem;
344 if (!mother.empty() && mother !=
"root") {
345 auto itM = itemLookup.find(mother);
346 if (itM != itemLookup.end()) {
347 parentItem = itM->second;
351 parentItem->addChild(thisItem);
357 for (
auto& [volName, vptr] : volMap) {
359 QTreeWidgetItem* item = itemLookup[volName];
361 auto* colorBtn =
new QPushButton(treeWidget);
363 colorBtn->setFixedSize(20, 20);
364 colorBtn->setFlat(
true);
365 colorBtn->setText(QString());
367 colorBtn->setStyleSheet(
368 QString(
"QPushButton { background-color: %1; border: 1px solid %2; }")
369 .arg(c.name(), palette().color(QPalette::Mid).name())
373 colorBtn->setProperty(
"volumeName", QString::fromStdString(volName));
374 connect(colorBtn, &QPushButton::clicked,
this, >ree::onColorButtonClicked);
376 treeWidget->setItemWidget(item, 1, colorBtn);
380 treeWidget->expandAll();
385void GTree::build_tree(std::unordered_map<std::string, G4Volume*> g4volumes_map) {
387 for (
auto [name, g4volume] : g4volumes_map) {
399 auto& system_tree = g4_systems_tree[system_name];
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);
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");
413void GTree::onItemChanged(QTreeWidgetItem* item,
int column) {
414 if (column != 0)
return;
417 QVariant v = item->data(2, Qt::UserRole);
423 bool visible = (item->checkState(0) == Qt::Checked);
425 QSignalBlocker blocker(treeWidget);
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);
434 const QString fullName = cv.toString();
437 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
440 set_visibility(fullName.toStdString(), visible);
449 const QString fullName = v.toString();
450 bool visible = (item->checkState(0) == Qt::Checked);
452 QSignalBlocker blocker(treeWidget);
455 set_visibility(fullName.toStdString(), visible);
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);
465 const QString childName = cv.toString();
468 child->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
471 set_visibility(childName.toStdString(), visible);
477void GTree::onColorButtonClicked() {
478 auto* btn = qobject_cast<QPushButton*>(sender());
482 const QString volName = btn->property(
"volumeName").toString();
483 if (volName.isEmpty())
486 QColor initial = Qt::white;
488 QColor c = QColorDialog::getColor(initial,
this, tr(
"Select color"));
494 QString(
"QPushButton { background-color: %1; border: 1px solid %2; }")
495 .arg(c.name(), palette().color(QPalette::Mid).name())
499 set_color(volName.toStdString(), c);
504void GTree::set_visibility(
const std::string& volumeName,
bool visible) {
505 std::string vis_int = visible ?
"1" :
"0";
507 std::string command =
"/vis/geometry/set/visibility " + volumeName +
" -1 " + vis_int;
510 if (volumeName == ROOTWORLDGVOLUMENAME) {
511 command =
"/vis/geometry/set/visibility " + volumeName +
" 0 " + vis_int;
518void GTree::set_color(
const std::string& volumeName,
const QColor& c) {
520 double currentOpacity = item ? item->
get_opacity() : 1.0;
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);
533int GTree::get_ndaughters(QTreeWidgetItem* item)
const {
535 return item->childCount();
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();
551void GTree::onTreeItemClicked(QTreeWidgetItem* item,
int ) {
556 bottomPanel->setVisible(
false);
557 current_volume_name.clear();
561 bottomPanel->setVisible(
true);
564 QVariant v = item->data(2, Qt::UserRole);
565 bool isVolume = v.isValid();
569 typeLabel->setText(QStringLiteral(
"<b>G4 Volume</b>"));
570 current_volume_name = v.toString().toStdString();
573 typeLabel->setText(QStringLiteral(
"<b>System</b>"));
574 current_volume_name.clear();
578 int nd = get_ndaughters(item);
579 daughtersLabel->setText(tr(
"Daughters: %1").arg(nd));
582 QString itemName = item->text(2);
583 nameLabel->setText(tr(
"Name: %1").arg(itemName));
587 styleButtons->setVisible(
true);
589 const std::string fullName = v.toString().toStdString();
593 materialLabel->setText(
594 tr(
"Material: %1").arg(QString::fromStdString(titem->
get_material()))
598 massLabel->setText(tr(
"Total Mass: %1 g").arg(mass));
601 massLabel->setText(tr(
"Total Mass: %1 kg").arg(mass / 1000));
604 if (volume < 1000000) {
605 volumeLabel->setText(tr(
"Volume: %1 cm3").arg(volume));
608 volumeLabel->setText(tr(
"Volume: %1 m3").arg(volume / 1000000));
611 densityLabel->setText(
612 tr(
"Average Density: %1 g / cm3").arg(titem->
get_density())
617 solidTypeLabel->setText(solidT.empty() ? QString() : tr(
"Solid: %1").arg(QString::fromStdString(solidT)));
619 const bool hasParams = !params.empty();
620 parametersLabel->setVisible(hasParams);
621 if (hasParams) parametersLabel->setHtml(formatParameters(solidT, params));
623 positionLabel->setText(pos.empty() ? QString() : tr(
"Position: %1").arg(QString::fromStdString(pos)));
625 rotationLabel->setText(rot.empty() ? QString() : tr(
"Rotation: %1").arg(QString::fromStdString(rot)));
627 motherLabel->setText(mom.empty() ? QString() : tr(
"Mother: %1").arg(QString::fromStdString(mom)));
629 descriptionLabel->setText(desc.empty() ? QString() : tr(
"Description: %1").arg(QString::fromStdString(desc)));
633 int sliderVal =
static_cast<int>(op * 100.0 + 0.5);
635 QSignalBlocker blocker(opacitySlider);
636 opacitySlider->setValue(sliderVal);
638 opacityLabel->setText(QString::number(op,
'f', 2));
639 opacitySlider->setVisible(
true);
645 inspectButton->setText(tr(
"Inspect %1").arg(leaf));
646 inspectButton->setVisible(
true);
648 if (drawOverlapsButton) {
650 drawOverlapsButton->setText(tr(
"Draw Logical Overlaps %1").arg(leaf));
651 drawOverlapsButton->setVisible(
true);
655 styleButtons->setVisible(
false);
656 opacitySlider->setVisible(
false);
657 if (inspectButton) inspectButton->setVisible(
false);
658 if (drawOverlapsButton) drawOverlapsButton->setVisible(
false);
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(
""));
675void GTree::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) {
684 onTreeItemClicked(current, 0);
688void GTree::changeStyle() {
691 if (current_volume_name.empty())
696 if (button_index == 3) {
708 if (button_index == 0) {
709 command =
"/vis/geometry/set/forceWireframe " + current_volume_name +
" 0 1 ";
711 else if (button_index == 1) {
712 command =
"/vis/geometry/set/forceSolid " + current_volume_name +
" 0 1 ";
714 else if (button_index == 2) {
715 command =
"/vis/geometry/set/forceCloud " + current_volume_name +
" 0 1 ";
727void GTree::centreTwinkle() {
731 twinkleVolumeName = current_volume_name;
739 twinkleTimer =
new QTimer(
this);
740 connect(twinkleTimer, &QTimer::timeout,
this, >ree::onTwinkleStep);
742 if (twinkleTimer->isActive())
743 twinkleTimer->stop();
745 twinkleTimer->start(180);
750void GTree::onTwinkleStep() {
751 constexpr int kSteps = 5;
752 static const QColor kFlash[kSteps] = {
755 QColor( 50, 255, 50),
756 QColor( 0, 220, 255),
757 QColor(220, 50, 255),
760 if (twinkleTick < kSteps) {
761 set_color(twinkleVolumeName, kFlash[twinkleTick]);
764 twinkleTimer->stop();
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);
784void GTree::inspectVolume() {
785 if (current_volume_name.empty())
return;
787 G4UImanager* uim = G4UImanager::GetUIpointer();
792 std::string driverName =
"TOOLSSG_QT_GLES";
793 std::string originalViewerName;
794 auto* vm = G4VisManager::GetInstance();
796 const auto* viewer = vm->GetCurrentViewer();
798 originalViewerName = viewer->GetName();
799 const auto* sh = viewer->GetSceneHandler();
801 const auto* gs = sh->GetGraphicsSystem();
802 if (gs) driverName = gs->GetNickname();
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");
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");
829 if (!originalViewerName.empty())
830 uim->ApplyCommand(
"/vis/viewer/select " + originalViewerName);
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."));
845void GTree::onOpacitySliderChanged(
int value) {
846 if (current_volume_name.empty())
849 double opacity = value / 100.0;
852 opacityLabel->setText(QString::number(opacity,
'f', 2));
855 set_opacity(current_volume_name, opacity);
859void GTree::set_opacity(
const std::string& volumeName,
double opacity) {
867 double g = c.greenF();
868 double b = c.blueF();
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);
Lightweight per-volume record used by GTree to populate the UI.
std::string get_motherVolume() const
Return the mother volume name.
static std::string vname_from_v4name(std::string v4name)
Extract the "leaf" volume name from a full volume name.
double get_opacity() const
Return the cached opacity (alpha) in [0,1].
std::string get_volDescription() const
Return the volume description.
std::string get_rotation() const
Return the placement rotation string with units.
double get_density() const
Return the cached density.
void set_color(const QColor &c)
Update the cached color.
void set_opacity(double opacity)
Update the cached opacity.
double get_volume() const
Return the cached volume.
bool get_visibility() const
Return the cached visibility state.
double get_mass() const
Return the cached mass.
QColor get_color() const
Return the cached RGB color.
std::string get_solidType() const
Return the solid type string (e.g. "G4Box").
static std::string system_from_v4name(std::string v4name)
Extract the system name from a full volume name.
std::string get_position() const
Return the placement position string with units.
std::string get_parameters() const
Return the solid parameters string with units.
std::string get_material() const
Return the cached material name.
G4Ttree_item(G4Volume *g4volume, const GVolume *gvolume=nullptr)
Construct a cached record for a single geometry volume.
std::string get_mother() const
Return the cached mother name.
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
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.
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 ROOTWORLDGVOLUMENAME
Option-set definition entry point for the GTree module.
constexpr const char * GTREE_LOGGER
void apply_uimanager_commands(const std::string &commands)