g4display
Loading...
Searching...
No Matches
g4displayview.cc
Go to the documentation of this file.
1#include "g4displayview.h"
2#include "g4display_options.h"
3#include "gutilities.h"
4
5#include <QPainter>
6
7using namespace g4display;
8
9// c++
10#include <algorithm>
11#include <iostream>
12#include <sstream>
13#include <string>
14using namespace std;
15using namespace gutilities;
16
17// c++ math
18#include <cmath>
19
20// geant4
21#include "G4UImanager.hh"
22#include "G4VisManager.hh"
23#include "G4VViewer.hh"
24
25// Implementation note:
26// Detailed Doxygen documentation for public behavior and slots is maintained in g4displayview.h (see rule 7).
27namespace {
28QColor colorFromG4Rgb(const std::string& rgb) {
29 std::istringstream stream(rgb);
30 double r = 0.05;
31 double g = 0.05;
32 double b = 0.26;
33 stream >> r >> g >> b;
34 return QColor::fromRgbF(std::clamp(r, 0.0, 1.0),
35 std::clamp(g, 0.0, 1.0),
36 std::clamp(b, 0.0, 1.0));
37}
38
39QString g4RgbFromColor(const QColor& color) {
40 return QString("%1 %2 %3")
41 .arg(color.redF(), 0, 'f', 5)
42 .arg(color.greenF(), 0, 'f', 5)
43 .arg(color.blueF(), 0, 'f', 5);
44}
45
46QIcon colorIcon(const QColor& color, const QColor& border) {
47 QPixmap pixmap(18, 18);
48 pixmap.fill(Qt::transparent);
49
50 QPainter painter(&pixmap);
51 painter.setRenderHint(QPainter::Antialiasing);
52 painter.setBrush(color);
53 painter.setPen(border);
54 painter.drawRoundedRect(pixmap.rect().adjusted(1, 1, -2, -2), 3, 3);
55 return QIcon(pixmap);
56}
57}
58
59G4DisplayView::G4DisplayView(const std::shared_ptr<GOptions>& gopts,
60 std::shared_ptr<GLogger> logger,
61 QWidget* parent)
62 : QWidget(parent), gopts(gopts), log(logger) {
63 log->debug(CONSTRUCTOR, "G4DisplayView");
64
65 // LCD font used to display theta/phi degrees next to sliders.
66 QFont flcd;
67 flcd.setFamilies({"Helvetica"}); // Qt6: prefer setFamilies over family string ctor
68 flcd.setPointSize(24);
69 flcd.setBold(true);
70
71 // Initial camera configuration comes from options; values are converted to degrees for UI.
72 G4Camera jcamera = getG4Camera(gopts);
73 double thetaValue = getG4Number(jcamera.theta);
74 double phiValue = getG4Number(jcamera.phi);
75 G4Light jlight = getG4Light(gopts);
76 double lightThetaValue = getG4Number(jlight.theta);
77 double lightPhiValue = getG4Number(jlight.phi);
78 G4View g4view = getG4View(gopts);
79 backgroundColor = colorFromG4Rgb(g4view.background);
80 cloudPoints = g4view.cloudPoints;
81
82 // Toggle buttons for common viewer/scene flags.
83 // Button 0 uses an SVG icon (set below); its title is left empty so only the SVG is shown.
84 vector<string> toggle_button_titles;
85 toggle_button_titles.emplace_back("");
86 toggle_button_titles.emplace_back("");
87 toggle_button_titles.emplace_back("");
88 toggle_button_titles.emplace_back("");
89
90 buttons_set1 = new GQTToggleButtonWidget(120, 120, 0, toggle_button_titles, false, this);
91 connect(buttons_set1, &GQTToggleButtonWidget::buttonPressedIndexChanged, this, &G4DisplayView::apply_buttons_set1);
92
93 buttons_set1->setSvgButtonIcon(0, ":/images/hidden_lines.svg", QSize(120, 120));
94 buttons_set1->setSvgButtonIcon(1, ":/images/anti_aliasing.svg", QSize(120, 120));
95 buttons_set1->setSvgButtonIcon(2, ":/images/auxiliary_edges.svg", QSize(120, 120));
96 buttons_set1->setSvgButtonIcon(3, ":/images/field_lines.svg", QSize(120, 120));
97
98 // Preset angle sets used by camera and light dropdowns.
99 QStringList theta_angle_Set;
100 for (int t = 0; t <= 180; t += 30) { theta_angle_Set << QString::number(t); }
101 QStringList phi_angle_Set;
102 for (int t = 0; t <= 360; t += 30) { phi_angle_Set << QString::number(t); }
103
104 // -------------------------
105 // Camera direction controls
106 // -------------------------
107
108 cameraTheta = new QSlider(Qt::Horizontal);
109 cameraTheta->setRange(0, 180);
110 cameraTheta->setSingleStep(1);
111 cameraTheta->setValue(thetaValue);
112 cameraTheta->setTracking(true); // updates while dragging
113
114 auto cameraThetaLabel = new QLabel(tr("θ"));
115
116 thetaLCD = new QLCDNumber(this);
117 thetaLCD->setFont(flcd);
118 thetaLCD->setMaximumSize(QSize(42, 32));
119 thetaLCD->setSegmentStyle(QLCDNumber::Flat);
120
121 thetaDropdown = new QComboBox(this);
122 thetaDropdown->addItems(theta_angle_Set);
123 thetaDropdown->setMaximumSize(QSize(100, 45));
124
125 auto cameraThetaLayout = new QHBoxLayout;
126 cameraThetaLayout->setContentsMargins(0, 0, 0, 0);
127 cameraThetaLayout->setSpacing(4);
128 cameraThetaLayout->addWidget(cameraThetaLabel);
129 cameraThetaLayout->addWidget(cameraTheta);
130 cameraThetaLayout->addWidget(thetaLCD);
131 cameraThetaLayout->addWidget(thetaDropdown);
132
133 cameraPhi = new QSlider(Qt::Horizontal);
134 cameraPhi->setRange(0, 360);
135 cameraPhi->setSingleStep(1);
136 cameraPhi->setValue(phiValue);
137 cameraPhi->setTracking(true); // updates while dragging
138
139 auto cameraPhiLabel = new QLabel(tr("ɸ"));
140
141 phiLCD = new QLCDNumber(this);
142 phiLCD->setFont(flcd);
143 phiLCD->setMaximumSize(QSize(42, 32));
144 phiLCD->setSegmentStyle(QLCDNumber::Flat);
145
146 phiDropdown = new QComboBox(this);
147 phiDropdown->addItems(phi_angle_Set);
148 phiDropdown->setMaximumSize(QSize(100, 45));
149
150 auto cameraPhiLayout = new QHBoxLayout;
151 cameraPhiLayout->setContentsMargins(0, 0, 0, 0);
152 cameraPhiLayout->setSpacing(4);
153 cameraPhiLayout->addWidget(cameraPhiLabel);
154 cameraPhiLayout->addWidget(cameraPhi);
155 cameraPhiLayout->addWidget(phiLCD);
156 cameraPhiLayout->addWidget(phiDropdown);
157
158 auto* readViewButton = new QPushButton(tr("Read View"), this);
159 readViewButton->setToolTip(tr("Sync sliders to current viewer orientation"));
160 auto* readViewRow = new QHBoxLayout;
161 readViewRow->setContentsMargins(0, 0, 0, 0);
162 readViewRow->addStretch(1);
163 readViewRow->addWidget(readViewButton);
164
165 QVBoxLayout* cameraDirectionLayout = new QVBoxLayout;
166 cameraDirectionLayout->setContentsMargins(6, 6, 6, 6);
167 cameraDirectionLayout->setSpacing(2);
168 cameraDirectionLayout->addLayout(cameraThetaLayout);
169 cameraDirectionLayout->addLayout(cameraPhiLayout);
170 cameraDirectionLayout->addLayout(readViewRow);
171
172 QGroupBox* cameraAnglesGroup = new QGroupBox(tr("Camera Direction"));
173 cameraAnglesGroup->setLayout(cameraDirectionLayout);
174
175 // Slider -> Geant4 command, and slider -> LCD.
176 connect(cameraTheta, &QSlider::valueChanged, this, &G4DisplayView::changeCameraDirection);
177 connect(cameraTheta, &QSlider::valueChanged, thetaLCD, qOverload<int>(&QLCDNumber::display));
178
179 // Dropdown -> Geant4 command, and dropdown -> slider sync.
180 connect(thetaDropdown, &QComboBox::currentTextChanged,
181 this, [this](const QString&) { setCameraDirection(0); });
182
183 connect(cameraPhi, &QSlider::valueChanged, this, &G4DisplayView::changeCameraDirection);
184 connect(cameraPhi, &QSlider::valueChanged, phiLCD, qOverload<int>(&QLCDNumber::display));
185
186 connect(phiDropdown, &QComboBox::currentTextChanged,
187 this, [this](const QString&) { setCameraDirection(1); });
188
189 connect(readViewButton, &QPushButton::clicked, this, &G4DisplayView::readCameraFromViewer);
190
191 // ---------------------
192 // View properties group
193 // ---------------------
194
195 // Projection: orthogonal or perspective (with typical angles).
196 QLabel* projLabel = new QLabel(tr("Projection:"));
197 perspectiveDropdown = new QComboBox;
198 perspectiveDropdown->addItem(tr("Orthogonal"));
199 perspectiveDropdown->addItem(tr("Perspective 30"));
200 perspectiveDropdown->addItem(tr("Perspective 45"));
201 perspectiveDropdown->addItem(tr("Perspective 60"));
202
203 // Sides per circle: maps to /vis/viewer/set/lineSegmentsPerCircle.
204 QLabel* sides_per_circlesLabel = new QLabel(tr("Sides per circle:"));
205 precisionDropdown = new QComboBox;
206 precisionDropdown->addItem(tr("50"));
207 precisionDropdown->addItem(tr("100"));
208 precisionDropdown->addItem(tr("200"));
209 precisionDropdown->addItem(tr("300"));
210 precisionDropdown->setCurrentIndex(0);
211
212 connect(perspectiveDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_projection);
213 connect(precisionDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_precision);
214
215 QVBoxLayout* resolutionAndPerspectiveLayout = new QVBoxLayout;
216 resolutionAndPerspectiveLayout->setContentsMargins(6, 6, 6, 6);
217 resolutionAndPerspectiveLayout->setSpacing(4);
218 resolutionAndPerspectiveLayout->addWidget(projLabel);
219 resolutionAndPerspectiveLayout->addWidget(perspectiveDropdown);
220 resolutionAndPerspectiveLayout->addWidget(sides_per_circlesLabel);
221 resolutionAndPerspectiveLayout->addWidget(precisionDropdown);
222
223 QGroupBox* propertyGroup = new QGroupBox(tr("View Properties"));
224 propertyGroup->setLayout(resolutionAndPerspectiveLayout);
225
226 QHBoxLayout* cameraAndPerspective = new QHBoxLayout;
227 cameraAndPerspective->setContentsMargins(0, 0, 0, 0);
228 cameraAndPerspective->setSpacing(6);
229 cameraAndPerspective->addWidget(cameraAnglesGroup);
230 cameraAndPerspective->addWidget(propertyGroup);
231
232 // -------------------------
233 // Light direction controls
234 // -------------------------
235
236 lightTheta = new QSlider(Qt::Horizontal);
237 lightTheta->setRange(0, 180);
238 lightTheta->setSingleStep(1);
239 lightTheta->setValue(lightThetaValue);
240 lightTheta->setTracking(true);
241
242 auto lightThetaLabel = new QLabel(tr("θ"));
243
244 lthetaLCD = new QLCDNumber(this);
245 lthetaLCD->setFont(flcd);
246 lthetaLCD->setMaximumSize(QSize(42, 32));
247 lthetaLCD->setSegmentStyle(QLCDNumber::Flat);
248
249 lthetaDropdown = new QComboBox(this);
250 lthetaDropdown->addItems(theta_angle_Set);
251 lthetaDropdown->setMaximumSize(QSize(100, 45));
252
253 auto lightThetaLayout = new QHBoxLayout;
254 lightThetaLayout->setContentsMargins(0, 0, 0, 0);
255 lightThetaLayout->setSpacing(4);
256 lightThetaLayout->addWidget(lightThetaLabel);
257 lightThetaLayout->addWidget(lightTheta);
258 lightThetaLayout->addWidget(lthetaLCD);
259 lightThetaLayout->addWidget(lthetaDropdown);
260
261 lightPhi = new QSlider(Qt::Horizontal);
262 lightPhi->setRange(0, 360);
263 lightPhi->setSingleStep(1);
264 lightPhi->setValue(lightPhiValue);
265 lightPhi->setTracking(true);
266
267 auto lightPhiLabel = new QLabel(tr("ɸ"));
268
269 lphiLCD = new QLCDNumber(this);
270 lphiLCD->setFont(flcd);
271 lphiLCD->setMaximumSize(QSize(42, 32));
272 lphiLCD->setSegmentStyle(QLCDNumber::Flat);
273
274 lphiDropdown = new QComboBox(this);
275 lphiDropdown->addItems(phi_angle_Set);
276 lphiDropdown->setMaximumSize(QSize(100, 45));
277
278 auto lightPhiLayout = new QHBoxLayout;
279 lightPhiLayout->setContentsMargins(0, 0, 0, 0);
280 lightPhiLayout->setSpacing(4);
281 lightPhiLayout->addWidget(lightPhiLabel);
282 lightPhiLayout->addWidget(lightPhi);
283 lightPhiLayout->addWidget(lphiLCD);
284 lightPhiLayout->addWidget(lphiDropdown);
285
286 auto lightDirectionLayout = new QVBoxLayout;
287 lightDirectionLayout->setContentsMargins(6, 6, 6, 6);
288 lightDirectionLayout->setSpacing(2);
289 lightDirectionLayout->addLayout(lightThetaLayout);
290 lightDirectionLayout->addLayout(lightPhiLayout);
291
292 QGroupBox* lightAnglesGroup = new QGroupBox(tr("Light Direction"));
293 lightAnglesGroup->setLayout(lightDirectionLayout);
294
295 connect(lightTheta, &QSlider::valueChanged, this, &G4DisplayView::changeLightDirection);
296 connect(lightTheta, &QSlider::valueChanged, lthetaLCD, qOverload<int>(&QLCDNumber::display));
297 connect(lthetaDropdown, &QComboBox::currentTextChanged,
298 this, [this](const QString&) { setLightDirection(0); });
299
300 connect(lightPhi, &QSlider::valueChanged, this, &G4DisplayView::changeLightDirection);
301 connect(lightPhi, &QSlider::valueChanged, lphiLCD, qOverload<int>(&QLCDNumber::display));
302 connect(lphiDropdown, &QComboBox::currentTextChanged,
303 this, [this](const QString&) { setLightDirection(1); });
304
305 // ----------------------
306 // Scene properties group
307 // ----------------------
308
309 QLabel* cullingLabel = new QLabel(tr("Culling:"));
310 cullingDropdown = new QComboBox;
311 cullingDropdown->addItem(tr("Reset"));
312 cullingDropdown->addItem(tr("Covered Daughters"));
313 cullingDropdown->addItem(tr("Density: 1 mg/cm3"));
314 cullingDropdown->addItem(tr("Density: 10 mg/cm3"));
315 cullingDropdown->addItem(tr("Density: 100 mg/cm3"));
316 cullingDropdown->addItem(tr("Density: 1 g/cm3"));
317 cullingDropdown->addItem(tr("Density: 10 g/cm3"));
318
319 QLabel* backgroundColorLabel = new QLabel(tr("Background Color:"));
320 backgroundColorDropdown = new QComboBox;
321 const auto addBackgroundPreset = [this](const QString& name, const QString& rgb) {
322 backgroundColorDropdown->addItem(name, rgb);
323 };
324 addBackgroundPreset(tr("lightslategray"), "0.46667 0.53333 0.60000");
325 addBackgroundPreset(tr("ghostwhite"), "0.97255 0.97255 1.00000");
326 addBackgroundPreset(tr("black"), "0.00000 0.00000 0.00000");
327 addBackgroundPreset(tr("navy"), "0.00000 0.00000 0.50196");
328 addBackgroundPreset(tr("whitesmoke"), "0.96078 0.96078 0.96078");
329 addBackgroundPreset(tr("lightskyblue"), "0.52941 0.80784 0.98039");
330 addBackgroundPreset(tr("deepskyblue"), "0.00000 0.74902 1.00000");
331 addBackgroundPreset(tr("lightsteelblue"), "0.69020 0.76863 0.87059");
332 addBackgroundPreset(tr("blueviolet"), "0.54118 0.16863 0.88627");
333 addBackgroundPreset(tr("turquoise"), "0.25098 0.87843 0.81569");
334 addBackgroundPreset(tr("mediumaquamarine"), "0.40000 0.80392 0.66667");
335 addBackgroundPreset(tr("springgreen"), "0.00000 1.00000 0.49804");
336 addBackgroundPreset(tr("lawngreen"), "0.48627 0.98824 0.00000");
337 addBackgroundPreset(tr("yellowgreen"), "0.60392 0.80392 0.19608");
338 addBackgroundPreset(tr("lemonchiffon"), "1.00000 0.98039 0.80392");
339 addBackgroundPreset(tr("antiquewhite"), "0.98039 0.92157 0.84314");
340 addBackgroundPreset(tr("wheat"), "0.96078 0.87059 0.70196");
341 addBackgroundPreset(tr("sienna"), "0.62745 0.32157 0.17647");
342 addBackgroundPreset(tr("snow"), "1.00000 0.98039 0.98039");
343 addBackgroundPreset(tr("floralwhite"), "1.00000 0.98039 0.94118");
344 addBackgroundPreset(tr("lightsalmon"), "1.00000 0.62745 0.47843");
345 addBackgroundPreset(tr("orchid"), "0.85490 0.43922 0.83922");
346 addBackgroundPreset(tr("plum"), "0.86667 0.62745 0.86667");
347 setBackgroundDropdownColor(backgroundColor);
348
349 backgroundColorButton = new QToolButton(this);
350 backgroundColorButton->setToolTip(tr("Choose background color"));
351 backgroundColorButton->setAutoRaise(true);
352 backgroundColorButton->setIconSize(QSize(18, 18));
353 setBackgroundButtonColor(backgroundColor);
354
355 auto* cloudPointsLabel = new QLabel(tr("Number of Cloud Points:"));
356 cloudPointsSpinBox = new QSpinBox(this);
357 cloudPointsSpinBox->setRange(1, 100000000);
358 cloudPointsSpinBox->setSingleStep(100);
359 cloudPointsSpinBox->setGroupSeparatorShown(true);
360 cloudPointsSpinBox->setValue(std::max(1, cloudPoints));
361
362 auto* explodeLabel = new QLabel(tr("Explode Factor:"));
363 explodeSlider = new QSlider(Qt::Horizontal);
364 explodeSlider->setRange(0, 100);
365 explodeSlider->setValue(0);
366 explodeSlider->setSingleStep(1);
367 explodeSlider->setPageStep(5);
368 explodeValueLabel = new QLabel(tr("1.00"));
369 explodeIntensityDropdown = new QComboBox;
370 explodeIntensityDropdown->addItem(tr("Low"), QVariant(45.0));
371 explodeIntensityDropdown->addItem(tr("Medium"), QVariant(15.0));
372 explodeIntensityDropdown->addItem(tr("High"), QVariant(5.0));
373 explodeIntensityDropdown->setCurrentIndex(1);
374
375 connect(cullingDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_culling);
376 connect(backgroundColorDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_background);
377 connect(backgroundColorButton, &QToolButton::clicked, this, &G4DisplayView::choose_background_color);
378 connect(cloudPointsSpinBox, qOverload<int>(&QSpinBox::valueChanged), this, &G4DisplayView::set_cloud_points);
379 connect(explodeSlider, &QSlider::valueChanged, this, &G4DisplayView::set_explode);
380 connect(explodeIntensityDropdown, &QComboBox::currentIndexChanged, this, &G4DisplayView::set_explode);
381
382 QVBoxLayout* sceneLayout = new QVBoxLayout;
383 sceneLayout->setContentsMargins(6, 6, 6, 6);
384 sceneLayout->setSpacing(4);
385 sceneLayout->addWidget(cullingLabel);
386 sceneLayout->addWidget(cullingDropdown);
387 sceneLayout->addWidget(backgroundColorLabel);
388 auto* backgroundLayout = new QHBoxLayout;
389 backgroundLayout->setContentsMargins(0, 0, 0, 0);
390 backgroundLayout->setSpacing(4);
391 backgroundLayout->addWidget(backgroundColorDropdown);
392 backgroundLayout->addWidget(backgroundColorButton);
393 sceneLayout->addLayout(backgroundLayout);
394 sceneLayout->addWidget(cloudPointsLabel);
395 sceneLayout->addWidget(cloudPointsSpinBox);
396 sceneLayout->addWidget(explodeLabel);
397 auto* explodeRow = new QHBoxLayout;
398 explodeRow->setContentsMargins(0, 0, 0, 0);
399 explodeRow->setSpacing(4);
400 explodeRow->addWidget(explodeSlider);
401 explodeRow->addWidget(explodeValueLabel);
402 explodeRow->addWidget(explodeIntensityDropdown);
403 sceneLayout->addLayout(explodeRow);
404
405 QGroupBox* spropertyGroup = new QGroupBox(tr("Scene Properties"));
406 spropertyGroup->setLayout(sceneLayout);
407
408 QHBoxLayout* lightAndProperties = new QHBoxLayout;
409 lightAndProperties->setContentsMargins(0, 0, 0, 0);
410 lightAndProperties->setSpacing(6);
411 lightAndProperties->addWidget(lightAnglesGroup);
412 lightAndProperties->addWidget(spropertyGroup);
413
414 // -------------------------
415 // Slice (cutaway) controls
416 // -------------------------
417
418 // X slice controls.
419 sliceXEdit = new QLineEdit(tr("0"));
420 sliceXEdit->setMaximumWidth(100);
421 sliceXActi = new QCheckBox(tr("&On"));
422 sliceXActi->setChecked(false);
423 sliceXInve = new QCheckBox(tr("&Flip"));
424 sliceXInve->setChecked(false);
425
426 auto sliceXLayout = new QHBoxLayout;
427 sliceXLayout->setContentsMargins(0, 0, 0, 0);
428 sliceXLayout->setSpacing(4);
429 sliceXLayout->addWidget(new QLabel(tr("X: ")));
430 sliceXLayout->addWidget(sliceXEdit);
431 sliceXLayout->addSpacing(16);
432 sliceXLayout->addWidget(sliceXActi);
433 sliceXLayout->addWidget(sliceXInve);
434 sliceXLayout->addStretch(1);
435
436 // Y slice controls.
437 sliceYEdit = new QLineEdit(tr("0"));
438 sliceYEdit->setMaximumWidth(100);
439 sliceYActi = new QCheckBox(tr("&On"));
440 sliceYActi->setChecked(false);
441 sliceYInve = new QCheckBox(tr("&Flip"));
442 sliceYInve->setChecked(false);
443
444 auto sliceYLayout = new QHBoxLayout;
445 sliceYLayout->setContentsMargins(0, 0, 0, 0);
446 sliceYLayout->setSpacing(4);
447 sliceYLayout->addWidget(new QLabel(tr("Y: ")));
448 sliceYLayout->addWidget(sliceYEdit);
449 sliceYLayout->addSpacing(16);
450 sliceYLayout->addWidget(sliceYActi);
451 sliceYLayout->addWidget(sliceYInve);
452 sliceYLayout->addStretch(1);
453
454 // Z slice controls.
455 sliceZEdit = new QLineEdit(tr("0"));
456 sliceZEdit->setMaximumWidth(100);
457 sliceZActi = new QCheckBox(tr("&On"));
458 sliceZActi->setChecked(false);
459 sliceZInve = new QCheckBox(tr("&Flip"));
460 sliceZInve->setChecked(false);
461
462 auto sliceZLayout = new QHBoxLayout;
463 sliceZLayout->setContentsMargins(0, 0, 0, 0);
464 sliceZLayout->setSpacing(4);
465 sliceZLayout->addWidget(new QLabel(tr("Z: ")));
466 sliceZLayout->addWidget(sliceZEdit);
467 sliceZLayout->addSpacing(16);
468 sliceZLayout->addWidget(sliceZActi);
469 sliceZLayout->addWidget(sliceZInve);
470 sliceZLayout->addStretch(1);
471
472 // Clear slice planes button.
473 QPushButton* clearSliceButton = new QPushButton(tr("Clear Slices"));
474 clearSliceButton->setToolTip("Clear Slice Planes");
475 clearSliceButton->setIcon(QIcon::fromTheme("edit-clear"));
476 clearSliceButton->setIconSize(QSize(16, 16));
477 connect(clearSliceButton, &QPushButton::clicked, this, &G4DisplayView::clearSlices);
478
479 // Slice composition mode: intersection vs union.
480 QGroupBox* sliceChoiceBox = new QGroupBox(tr("Slices Style"));
481 sliceSectn = new QRadioButton(tr("&Intersection"), sliceChoiceBox);
482 sliceUnion = new QRadioButton(tr("&Union"), sliceChoiceBox);
483 sliceSectn->setChecked(true);
484
485 connect(sliceSectn, &QRadioButton::toggled, this, &G4DisplayView::slice);
486 connect(sliceUnion, &QRadioButton::toggled, this, &G4DisplayView::slice);
487
488 auto sliceChoiceLayout = new QHBoxLayout;
489 sliceChoiceLayout->setContentsMargins(6, 4, 6, 4);
490 sliceChoiceLayout->setSpacing(8);
491 sliceChoiceLayout->addWidget(sliceSectn);
492 sliceChoiceLayout->addWidget(sliceUnion);
493 sliceChoiceBox->setLayout(sliceChoiceLayout);
494
495 // Slices layout.
496 auto sliceLayout = new QVBoxLayout;
497 sliceLayout->setContentsMargins(0, 0, 0, 0);
498 sliceLayout->setSpacing(3);
499 sliceLayout->addLayout(sliceXLayout);
500 sliceLayout->addLayout(sliceYLayout);
501 sliceLayout->addLayout(sliceZLayout);
502 sliceLayout->addWidget(sliceChoiceBox);
503 sliceLayout->addWidget(clearSliceButton);
504
505 // Connect slice UI signals to slice recomputation.
506 connect(sliceXEdit, &QLineEdit::returnPressed, this, &G4DisplayView::slice);
507 connect(sliceYEdit, &QLineEdit::returnPressed, this, &G4DisplayView::slice);
508 connect(sliceZEdit, &QLineEdit::returnPressed, this, &G4DisplayView::slice);
509
510#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) // Qt ≤ 6.6
511 connect(sliceXActi, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
512 connect(sliceYActi, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
513 connect(sliceZActi, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
514 connect(sliceXInve, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
515 connect(sliceYInve, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
516 connect(sliceZInve, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
517#else // Qt ≥ 6.7
518 connect(sliceXActi, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
519 connect(sliceYActi, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
520 connect(sliceZActi, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
521 connect(sliceXInve, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
522 connect(sliceYInve, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
523 connect(sliceZInve, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
524#endif
525
526 // ------------------------------
527 // Field line precision controls
528 // ------------------------------
529
530 QGroupBox* fieldPrecisionBox = new QGroupBox(tr("Number of Field Points"));
531 field_npoints = new QLineEdit(QString::number(field_NPOINTS), this);
532 field_npoints->setMaximumWidth(40);
533
534 QFont font = field_npoints->font();
535 font.setPointSize(18);
536 field_npoints->setFont(font);
537
538 connect(field_npoints, &QLineEdit::returnPressed, this, &G4DisplayView::field_precision_changed);
539
540 // Buttons + field point count UI combined.
541 auto fieldPointsHBox = new QHBoxLayout;
542 fieldPointsHBox->setContentsMargins(6, 4, 6, 4);
543 fieldPointsHBox->setSpacing(4);
544 fieldPointsHBox->addWidget(field_npoints);
545 fieldPrecisionBox->setLayout(fieldPointsHBox);
546
547 auto buttons_field_HBox = new QHBoxLayout;
548 buttons_field_HBox->setContentsMargins(0, 0, 0, 0);
549 buttons_field_HBox->setSpacing(6);
550 buttons_field_HBox->addWidget(buttons_set1);
551 buttons_field_HBox->addWidget(fieldPrecisionBox);
552 fieldPrecisionBox->setMaximumHeight(3 * buttons_set1->height());
553 fieldPrecisionBox->setMaximumWidth(140);
554
555 // -------------------------
556 // Assemble final tab layout
557 // -------------------------
558
559 auto mainLayout = new QVBoxLayout;
560 mainLayout->setContentsMargins(6, 6, 6, 6);
561 mainLayout->setSpacing(6);
562 mainLayout->addLayout(buttons_field_HBox);
563 mainLayout->addLayout(cameraAndPerspective);
564 mainLayout->addLayout(lightAndProperties);
565 mainLayout->addLayout(sliceLayout);
566 setLayout(mainLayout);
567}
568
569void G4DisplayView::changeCameraDirection() {
570 // Construct the Geant4 command using the current slider values and send it to the UI manager.
571 string command = "/vis/viewer/set/viewpointThetaPhi " +
572 to_string(cameraTheta->value()) + " " +
573 to_string(cameraPhi->value());
574 G4UImanager::GetUIpointer()->ApplyCommand(command);
575}
576
577void G4DisplayView::setCameraDirection(int which) {
578 // Dropdown values are interpreted as degrees; command is issued first, then the slider is synced.
579 string thetaValue = thetaDropdown->currentText().toStdString();
580 string phiValue = phiDropdown->currentText().toStdString();
581
582 string command = "/vis/viewer/set/viewpointThetaPhi " + thetaValue + " " + phiValue;
583 G4UImanager::GetUIpointer()->ApplyCommand(command);
584
585 int thetaDeg = thetaDropdown->currentText().toInt();
586 int phiDeg = phiDropdown->currentText().toInt();
587
588 if (cameraTheta && which == 0) cameraTheta->setValue(thetaDeg);
589 if (cameraPhi && which == 1) cameraPhi->setValue(phiDeg);
590}
591
592void G4DisplayView::set_projection() {
593 // Dropdown selections map to Geant4 projection parameters: orthogonal (o) or perspective (p, angle).
594 string value = perspectiveDropdown->currentText().toStdString();
595
596 string g4perspective = "o";
597 string g4perpvalue = "0";
598 if (value.find("Perspective") != string::npos) {
599 g4perspective = "p";
600 // Second token after space is the angle.
601 g4perpvalue = value.substr(value.find(" ") + 1);
602 }
603
604 string command = "/vis/viewer/set/projection " + g4perspective + " " + g4perpvalue;
605 G4UImanager::GetUIpointer()->ApplyCommand(command);
606}
607
608void G4DisplayView::set_precision() {
609 // Viewer circle segmentation impacts curved primitive smoothness.
610 string value = precisionDropdown->currentText().toStdString();
611
612 string command = "/vis/viewer/set/lineSegmentsPerCircle " + value;
613 G4UImanager::GetUIpointer()->ApplyCommand(command);
614 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/flush");
615}
616
617void G4DisplayView::set_culling() {
618 // Culling configuration uses Geant4 viewer culling commands.
619 string value = cullingDropdown->currentText().toStdString();
620 int index = cullingDropdown->currentIndex() - 2;
621
622 if (value.find("Reset") != string::npos) {
623 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling global true");
624 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling density false");
625 }
626 else if (value.find("Daughters") != string::npos) {
627 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling coveredDaughters true");
628 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling density false");
629 }
630 else {
631 // Density-based thresholds (unit assumed by Geant4 command expectations).
632 vector<double> density = {0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0};
633 string command = "/vis/viewer/set/culling density true " + to_string(density[index]);
634 G4UImanager::GetUIpointer()->ApplyCommand(command);
635 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/flush");
636 log->info(0, command);
637 }
638}
639
640void G4DisplayView::syncViewToOptions() {
641 if (!gopts) return;
642 const auto g4view = getG4View(gopts);
643 const QString s = QString("{driver: \"%1\", dimension: \"%2\", position: \"%3\", "
644 "segsPerCircle: %4, background: \"%5\", cloudPoints: %6}")
645 .arg(QString::fromStdString(g4view.driver))
646 .arg(QString::fromStdString(g4view.dimension))
647 .arg(QString::fromStdString(g4view.position))
648 .arg(g4view.segsPerCircle)
649 .arg(g4RgbFromColor(backgroundColor))
650 .arg(cloudPoints);
651 gopts->setOptionValueFromString("g4view", s.toStdString());
652}
653
654void G4DisplayView::setBackgroundButtonColor(const QColor& color) {
655 if (!backgroundColorButton) { return; }
656
657 backgroundColorButton->setIcon(colorIcon(color, palette().color(QPalette::Mid)));
658}
659
660void G4DisplayView::setBackgroundDropdownColor(const QColor& color) {
661 if (!backgroundColorDropdown) { return; }
662
663 QSignalBlocker blocker(backgroundColorDropdown);
664 const QString wanted = g4RgbFromColor(color);
665 for (int i = 0; i < backgroundColorDropdown->count(); ++i) {
666 const QColor candidate = colorFromG4Rgb(backgroundColorDropdown->itemData(i).toString().toStdString());
667 if (qAbs(candidate.redF() - color.redF()) < 0.0001 &&
668 qAbs(candidate.greenF() - color.greenF()) < 0.0001 &&
669 qAbs(candidate.blueF() - color.blueF()) < 0.0001) {
670 backgroundColorDropdown->setCurrentIndex(i);
671 return;
672 }
673 }
674
675 backgroundColorDropdown->addItem(tr("custom"), wanted);
676 backgroundColorDropdown->setCurrentIndex(backgroundColorDropdown->count() - 1);
677}
678
679void G4DisplayView::set_background() {
680 const QString g4value = backgroundColorDropdown->currentData().toString();
681 if (g4value.isEmpty()) { return; }
682
683 backgroundColor = colorFromG4Rgb(g4value.toStdString());
684 setBackgroundButtonColor(backgroundColor);
685
686 string command = "/vis/viewer/set/background " + g4value.toStdString();
687 G4UImanager::GetUIpointer()->ApplyCommand(command);
688 syncViewToOptions();
689}
690
691void G4DisplayView::choose_background_color() {
692 const QColor selected = QColorDialog::getColor(backgroundColor, this, tr("Select background color"));
693 if (!selected.isValid()) { return; }
694
695 backgroundColor = selected;
696 setBackgroundButtonColor(backgroundColor);
697 setBackgroundDropdownColor(backgroundColor);
698
699 const QString g4value = g4RgbFromColor(backgroundColor);
700 const string command = "/vis/viewer/set/background " + g4value.toStdString();
701 G4UImanager::GetUIpointer()->ApplyCommand(command);
702 syncViewToOptions();
703}
704
705void G4DisplayView::set_cloud_points() {
706 if (!cloudPointsSpinBox) { return; }
707
708 cloudPoints = cloudPointsSpinBox->value();
709 const string command = "/vis/viewer/set/numberOfCloudPoints " + to_string(cloudPoints);
710 G4UImanager::GetUIpointer()->ApplyCommand(command);
711 syncViewToOptions();
712}
713
714void G4DisplayView::showEvent(QShowEvent* event) {
715 QWidget::showEvent(event);
716 set_background();
717 set_cloud_points();
718 readCameraFromViewer();
719
720 // Light direction: use g4light values if specified, otherwise follow camera.
721 const auto g4light = getG4Light(gopts);
722 const double toDegrees = 180.0 / M_PI;
723 const double lightThetaDeg = getG4Number(g4light.theta) * toDegrees;
724 const double lightPhiDeg = getG4Number(g4light.phi) * toDegrees;
725
726 const int lt = (lightThetaDeg == 0.0 && lightPhiDeg == 0.0)
727 ? cameraTheta->value()
728 : static_cast<int>(std::round(lightThetaDeg));
729 const int lp = (lightThetaDeg == 0.0 && lightPhiDeg == 0.0)
730 ? cameraPhi->value()
731 : static_cast<int>(std::round(lightPhiDeg));
732
733 QSignalBlocker blt(lightTheta);
734 QSignalBlocker blp(lightPhi);
735 lightTheta->setValue(lt);
736 lightPhi->setValue(lp);
737 lthetaLCD->display(lt);
738 lphiLCD->display(lp);
739}
740
741void G4DisplayView::changeLightDirection() {
742 // Construct the Geant4 command using the current slider values and send it to the UI manager.
743 string command = "/vis/viewer/set/lightsThetaPhi " +
744 to_string(lightTheta->value()) + " " +
745 to_string(lightPhi->value());
746 G4UImanager::GetUIpointer()->ApplyCommand(command);
747}
748
749void G4DisplayView::setLightDirection(int which) {
750 // Dropdown values are interpreted as degrees; command is issued first, then the slider is synced.
751 string thetaValue = lthetaDropdown->currentText().toStdString();
752 string phiValue = lphiDropdown->currentText().toStdString();
753
754 string command = "/vis/viewer/set/lightsThetaPhi " + thetaValue + " " + phiValue;
755 G4UImanager::GetUIpointer()->ApplyCommand(command);
756
757 int thetaDeg = lthetaDropdown->currentText().toInt();
758 int phiDeg = lphiDropdown->currentText().toInt();
759
760 if (lightTheta && which == 0) lightTheta->setValue(thetaDeg);
761 if (lightPhi && which == 1) lightPhi->setValue(phiDeg);
762}
763
764void G4DisplayView::slice() {
765 G4UImanager* g4uim = G4UImanager::GetUIpointer();
766 if (g4uim == nullptr) { return; }
767
768 // Reset existing planes before applying the newly requested slice configuration.
769 g4uim->ApplyCommand("/vis/viewer/clearCutawayPlanes");
770
771 // Select how multiple planes combine.
772 if (sliceSectn->isChecked()) { g4uim->ApplyCommand("/vis/viewer/set/cutawayMode intersection"); }
773 else if (sliceUnion->isChecked()) { g4uim->ApplyCommand("/vis/viewer/set/cutawayMode union"); }
774
775 // Clear again to ensure mode change does not retain previously-defined planes.
776 g4uim->ApplyCommand("/vis/viewer/clearCutawayPlanes");
777
778 // For each enabled axis, add a plane at the requested position. Values are interpreted as mm.
779 if (sliceXActi->isChecked()) {
780 string command = "/vis/viewer/addCutawayPlane " + sliceXEdit->text().toStdString() + " 0 0 mm " +
781 to_string(sliceXInve->isChecked() ? -1 : 1) + " 0 0 ";
782 cout << "X " << command << endl;
783 g4uim->ApplyCommand(command);
784 }
785
786 if (sliceYActi->isChecked()) {
787 string command = "/vis/viewer/addCutawayPlane 0 " + sliceYEdit->text().toStdString() + " 0 mm 0 " +
788 to_string(sliceYInve->isChecked() ? -1 : 1) + " 0 ";
789 cout << "Y " << command << endl;
790 g4uim->ApplyCommand(command);
791 }
792
793 if (sliceZActi->isChecked()) {
794 string command = "/vis/viewer/addCutawayPlane 0 0 " + sliceZEdit->text().toStdString() + " mm 0 0 " +
795 to_string(sliceZInve->isChecked() ? -1 : 1);
796 cout << "Z " << command << endl;
797 g4uim->ApplyCommand(command);
798 }
799}
800
801void G4DisplayView::clearSlices() {
802 // Clear cutaway planes in the viewer and reset activation UI state.
803 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/clearCutawayPlanes");
804
805 // NOTE: Only X is reset here in the current implementation; Y/Z are left unchanged.
806 // This behavior is preserved intentionally (no logic changes).
807 sliceXActi->setChecked(false);
808}
809
810void G4DisplayView::apply_buttons_set1(int index) {
811 G4UImanager* g4uim = G4UImanager::GetUIpointer();
812 if (g4uim == nullptr) { return; }
813
814 bool button_state = buttons_set1->lastButtonState();
815
816 if (index == 0) {
817 // Hidden edges on/off.
818 string command = string("/vis/viewer/set/hiddenEdge") + (button_state ? " 1" : " 0");
819 g4uim->ApplyCommand(command);
820 g4uim->ApplyCommand("/vis/viewer/flush");
821 }
822 else if (index == 1) {
823 // Anti-aliasing: handled via OpenGL state for viewers where it applies.
824 if (button_state == 0) {
825 glDisable(GL_LINE_SMOOTH);
826 glDisable(GL_POLYGON_SMOOTH);
827 }
828 else {
829 glEnable(GL_LINE_SMOOTH);
830 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
831 glEnable(GL_POLYGON_SMOOTH);
832 glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
833 }
834 }
835 else if (index == 2) {
836 // Auxiliary edges typically implies hidden edges; keep UI state coherent.
837 string command = string("/vis/viewer/set/auxiliaryEdge") + (button_state ? " 1" : " 0");
838 g4uim->ApplyCommand(command);
839
840 command = string("/vis/viewer/set/hiddenEdge") + (button_state ? " 1" : " 0");
841 g4uim->ApplyCommand(command);
842
843 if (buttons_set1->buttonStatus(0) != button_state) { buttons_set1->toggleButton(0); }
844 }
845 else if (index == 3) {
846 // Magnetic field line visualization model.
847 if (button_state == 0) {
848 string command = string("/vis/scene/activateModel Field 0");
849 g4uim->ApplyCommand(command);
850 g4uim->ApplyCommand("/vis/scene/removeModel Field");
851 }
852 else {
853 string npoints = to_string(field_NPOINTS);
854 string command = string("/vis/scene/add/magneticField ") + npoints;
855 g4uim->ApplyCommand(command);
856 }
857 }
858}
859
860void G4DisplayView::set_explode() {
861 if (!explodeSlider) return;
862
863 const double divisor = (explodeIntensityDropdown && explodeIntensityDropdown->currentData().toDouble() > 0)
864 ? explodeIntensityDropdown->currentData().toDouble()
865 : 15.0;
866 const int boom = explodeSlider->value();
867 const double xf = 1.0 + static_cast<double>(boom) / divisor;
868
869 if (explodeValueLabel)
870 explodeValueLabel->setText(QString::number(xf, 'f', 2));
871
872 G4UImanager* g4uim = G4UImanager::GetUIpointer();
873 if (!g4uim) return;
874 g4uim->ApplyCommand("/vis/viewer/set/explodeFactor " + QString::number(xf, 'f', 2).toStdString());
875 g4uim->ApplyCommand("/vis/viewer/flush");
876}
877
878void G4DisplayView::readCameraFromViewer() {
879 auto* vm = G4VisManager::GetInstance();
880 if (!vm) return;
881 const auto* viewer = vm->GetCurrentViewer();
882 if (!viewer) return;
883
884 const G4Vector3D& vp = viewer->GetViewParameters().GetViewpointDirection();
885
886 const double cosTheta = std::clamp(vp.z(), -1.0, 1.0);
887 const double thetaDeg = std::acos(cosTheta) * 180.0 / M_PI;
888 double phiDeg = std::atan2(vp.y(), vp.x()) * 180.0 / M_PI;
889 if (phiDeg < 0.0) phiDeg += 360.0;
890
891 const int thetaInt = static_cast<int>(std::round(thetaDeg));
892 const int phiInt = static_cast<int>(std::round(phiDeg));
893
894 QSignalBlocker bt(cameraTheta);
895 QSignalBlocker bp(cameraPhi);
896 cameraTheta->setValue(thetaInt);
897 cameraPhi->setValue(phiInt);
898 thetaLCD->display(thetaInt);
899 phiLCD->display(phiInt);
900}
901
902void G4DisplayView::field_precision_changed() {
903 G4UImanager* g4uim = G4UImanager::GetUIpointer();
904 if (g4uim == nullptr) { return; }
905
906 // Parse the updated point count from the UI.
907 field_NPOINTS = field_npoints->text().toInt();
908
909 // If field lines are currently active, re-issue the field model to apply the new point count.
910 if (buttons_set1->buttonStatus(3) == 1) {
911 string command = string("vis/scene/activateModel Field 0");
912 g4uim->ApplyCommand(command);
913 g4uim->ApplyCommand("/vis/scene/removeModel Field");
914
915 string npoints = to_string(field_NPOINTS);
916 command = string("/vis/scene/add/magneticField ") + npoints;
917 G4UImanager::GetUIpointer()->ApplyCommand(command);
918 }
919}
G4DisplayView(const std::shared_ptr< GOptions > &gopts, std::shared_ptr< GLogger > logger, QWidget *parent=nullptr)
Construct the view-control tab.
void showEvent(QShowEvent *event) override
void debug(debug_type type, Args &&... args) const
void info(int level, Args &&... args) const
void setOptionValueFromString(const std::string &optionName, const std::string &possibleYamlNode)
void toggleButton(int index)
void setSvgButtonIcon(int index, const QString &svgResourcePath, const QSize &iconSize=QSize())
void buttonPressedIndexChanged(int index)
bool buttonStatus(int index) const
bool lastButtonState() const
Option structures and helpers for g4display configuration.
Declaration of the G4DisplayView tab widget.
event
CONSTRUCTOR
G4Light getG4Light(const std::shared_ptr< GOptions > &gopts)
Read the g4light option node and return a projected G4Light struct.
G4View getG4View(const std::shared_ptr< GOptions > &gopts)
Read the g4view option node and return a projected G4View struct.
G4Camera getG4Camera(const std::shared_ptr< GOptions > &gopts)
Read the g4camera option node and return a projected G4Camera struct.
double getG4Number(const string &v, bool warnIfNotUnit=false)
constexpr const char * to_string(randomModel m) noexcept
Camera angle configuration derived from the g4camera option node.
Light angle configuration derived from the g4light option node.
Viewer configuration derived from the g4view option node.