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
5using namespace g4display;
6
7// c++
8#include <iostream>
9#include <string>
10using namespace std;
11using namespace gutilities;
12
13// geant4
14#include "G4UImanager.hh" // Geant4 UI manager access
15
16// Implementation note:
17// Detailed Doxygen documentation for public behavior and slots is maintained in g4displayview.h (see rule 7).
18
19G4DisplayView::G4DisplayView(const std::shared_ptr<GOptions>& gopts,
20 std::shared_ptr<GLogger> logger,
21 QWidget* parent)
22 : QWidget(parent), log(logger) {
23 log->debug(CONSTRUCTOR, "G4DisplayView");
24
25 // LCD font used to display theta/phi degrees next to sliders.
26 QFont flcd;
27 flcd.setFamilies({"Helvetica"}); // Qt6: prefer setFamilies over family string ctor
28 flcd.setPointSize(32);
29 flcd.setBold(true);
30
31 // Initial camera configuration comes from options; values are converted to degrees for UI.
32 G4Camera jcamera = getG4Camera(gopts);
33 double thetaValue = getG4Number(jcamera.theta);
34 double phiValue = getG4Number(jcamera.phi);
35 G4Light jlight = getG4Light(gopts);
36 double lightThetaValue = getG4Number(jlight.theta);
37 double lightPhiValue = getG4Number(jlight.phi);
38
39 // Toggle buttons for common viewer/scene flags.
40 vector<string> toggle_button_titles;
41 toggle_button_titles.emplace_back("Hidden\nLines");
42 toggle_button_titles.emplace_back("Anti\nAliasing");
43 toggle_button_titles.emplace_back("Auxiliary\nEdges");
44 toggle_button_titles.emplace_back("Field\nLines");
45 toggle_button_titles.emplace_back("Axes");
46 toggle_button_titles.emplace_back("Scale");
47
48 buttons_set1 = new GQTToggleButtonWidget(80, 80, 20, toggle_button_titles, false, this);
49 connect(buttons_set1, &GQTToggleButtonWidget::buttonPressedIndexChanged, this, &G4DisplayView::apply_buttons_set1);
50
51 // Preset angle sets used by camera and light dropdowns.
52 QStringList theta_angle_Set;
53 for (int t = 0; t <= 180; t += 30) { theta_angle_Set << QString::number(t); }
54 QStringList phi_angle_Set;
55 for (int t = 0; t <= 360; t += 30) { phi_angle_Set << QString::number(t); }
56
57 // -------------------------
58 // Camera direction controls
59 // -------------------------
60
61 cameraTheta = new QSlider(Qt::Horizontal);
62 cameraTheta->setRange(0, 180);
63 cameraTheta->setSingleStep(1);
64 cameraTheta->setValue(thetaValue);
65 cameraTheta->setTracking(true); // updates while dragging
66
67 auto cameraThetaLabel = new QLabel(tr("θ"));
68
69 QLCDNumber* theta_LCD = new QLCDNumber(this);
70 theta_LCD->setFont(flcd);
71 theta_LCD->setMaximumSize(QSize(45, 45));
72 theta_LCD->setSegmentStyle(QLCDNumber::Flat);
73
74 thetaDropdown = new QComboBox(this);
75 thetaDropdown->addItems(theta_angle_Set);
76 thetaDropdown->setMaximumSize(QSize(100, 45));
77
78 auto cameraThetaLayout = new QHBoxLayout;
79 cameraThetaLayout->addWidget(cameraThetaLabel);
80 cameraThetaLayout->addWidget(cameraTheta);
81 cameraThetaLayout->addWidget(theta_LCD);
82 cameraThetaLayout->addWidget(thetaDropdown);
83
84 cameraPhi = new QSlider(Qt::Horizontal);
85 cameraPhi->setRange(0, 360);
86 cameraPhi->setSingleStep(1);
87 cameraPhi->setValue(phiValue);
88 cameraPhi->setTracking(true); // updates while dragging
89
90 auto cameraPhiLabel = new QLabel(tr("ɸ"));
91
92 QLCDNumber* phi_LCD = new QLCDNumber(this);
93 phi_LCD->setFont(flcd);
94 phi_LCD->setMaximumSize(QSize(45, 45));
95 phi_LCD->setSegmentStyle(QLCDNumber::Flat);
96
97 phiDropdown = new QComboBox(this);
98 phiDropdown->addItems(phi_angle_Set);
99 phiDropdown->setMaximumSize(QSize(100, 45));
100
101 auto cameraPhiLayout = new QHBoxLayout;
102 cameraPhiLayout->addWidget(cameraPhiLabel);
103 cameraPhiLayout->addWidget(cameraPhi);
104 cameraPhiLayout->addWidget(phi_LCD);
105 cameraPhiLayout->addWidget(phiDropdown);
106
107 QVBoxLayout* cameraDirectionLayout = new QVBoxLayout;
108 cameraDirectionLayout->addLayout(cameraThetaLayout);
109 cameraDirectionLayout->addSpacing(12);
110 cameraDirectionLayout->addLayout(cameraPhiLayout);
111
112 QGroupBox* cameraAnglesGroup = new QGroupBox(tr("Camera Direction"));
113 cameraAnglesGroup->setLayout(cameraDirectionLayout);
114
115 // Slider -> Geant4 command, and slider -> LCD.
116 connect(cameraTheta, &QSlider::valueChanged, this, &G4DisplayView::changeCameraDirection);
117 connect(cameraTheta, &QSlider::valueChanged, theta_LCD, qOverload<int>(&QLCDNumber::display));
118
119 // Dropdown -> Geant4 command, and dropdown -> slider sync.
120 connect(thetaDropdown, &QComboBox::currentTextChanged,
121 this, [this](const QString&) { setCameraDirection(0); });
122
123 connect(cameraPhi, &QSlider::valueChanged, this, &G4DisplayView::changeCameraDirection);
124 connect(cameraPhi, &QSlider::valueChanged, phi_LCD, qOverload<int>(&QLCDNumber::display));
125
126 connect(phiDropdown, &QComboBox::currentTextChanged,
127 this, [this](const QString&) { setCameraDirection(1); });
128
129 // ---------------------
130 // View properties group
131 // ---------------------
132
133 // Projection: orthogonal or perspective (with typical angles).
134 QLabel* projLabel = new QLabel(tr("Projection:"));
135 perspectiveDropdown = new QComboBox;
136 perspectiveDropdown->addItem(tr("Orthogonal"));
137 perspectiveDropdown->addItem(tr("Perspective 30"));
138 perspectiveDropdown->addItem(tr("Perspective 45"));
139 perspectiveDropdown->addItem(tr("Perspective 60"));
140
141 // Sides per circle: maps to /vis/viewer/set/lineSegmentsPerCircle.
142 QLabel* sides_per_circlesLabel = new QLabel(tr("Sides per circle:"));
143 precisionDropdown = new QComboBox;
144 precisionDropdown->addItem(tr("50"));
145 precisionDropdown->addItem(tr("100"));
146 precisionDropdown->addItem(tr("200"));
147 precisionDropdown->addItem(tr("300"));
148 precisionDropdown->setCurrentIndex(0);
149
150 connect(perspectiveDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_projection);
151 connect(precisionDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_precision);
152
153 QVBoxLayout* resolutionAndPerspectiveLayout = new QVBoxLayout;
154 resolutionAndPerspectiveLayout->addWidget(projLabel);
155 resolutionAndPerspectiveLayout->addWidget(perspectiveDropdown);
156 resolutionAndPerspectiveLayout->addSpacing(12);
157 resolutionAndPerspectiveLayout->addWidget(sides_per_circlesLabel);
158 resolutionAndPerspectiveLayout->addWidget(precisionDropdown);
159
160 QGroupBox* propertyGroup = new QGroupBox(tr("View Properties"));
161 propertyGroup->setLayout(resolutionAndPerspectiveLayout);
162
163 QHBoxLayout* cameraAndPerspective = new QHBoxLayout;
164 cameraAndPerspective->addWidget(cameraAnglesGroup);
165 cameraAndPerspective->addSpacing(12);
166 cameraAndPerspective->addWidget(propertyGroup);
167
168 // -------------------------
169 // Light direction controls
170 // -------------------------
171
172 lightTheta = new QSlider(Qt::Horizontal);
173 lightTheta->setRange(0, 180);
174 lightTheta->setSingleStep(1);
175 lightTheta->setValue(lightThetaValue);
176 lightTheta->setTracking(true);
177
178 auto lightThetaLabel = new QLabel(tr("θ"));
179
180 QLCDNumber* ltheta_LCD = new QLCDNumber(this);
181 ltheta_LCD->setFont(flcd);
182 ltheta_LCD->setMaximumSize(QSize(45, 45));
183 ltheta_LCD->setSegmentStyle(QLCDNumber::Flat);
184
185 lthetaDropdown = new QComboBox(this);
186 lthetaDropdown->addItems(theta_angle_Set);
187 lthetaDropdown->setMaximumSize(QSize(100, 45));
188
189 auto lightThetaLayout = new QHBoxLayout;
190 lightThetaLayout->addWidget(lightThetaLabel);
191 lightThetaLayout->addWidget(lightTheta);
192 lightThetaLayout->addWidget(ltheta_LCD);
193 lightThetaLayout->addWidget(lthetaDropdown);
194
195 lightPhi = new QSlider(Qt::Horizontal);
196 lightPhi->setRange(0, 360);
197 lightPhi->setSingleStep(1);
198 lightPhi->setValue(lightPhiValue);
199 lightPhi->setTracking(true);
200
201 auto lightPhiLabel = new QLabel(tr("ɸ"));
202
203 QLCDNumber* lphi_LCD = new QLCDNumber(this);
204 lphi_LCD->setFont(flcd);
205 lphi_LCD->setMaximumSize(QSize(45, 45));
206 lphi_LCD->setSegmentStyle(QLCDNumber::Flat);
207
208 lphiDropdown = new QComboBox(this);
209 lphiDropdown->addItems(phi_angle_Set);
210 lphiDropdown->setMaximumSize(QSize(100, 45));
211
212 auto lightPhiLayout = new QHBoxLayout;
213 lightPhiLayout->addWidget(lightPhiLabel);
214 lightPhiLayout->addWidget(lightPhi);
215 lightPhiLayout->addWidget(lphi_LCD);
216 lightPhiLayout->addWidget(lphiDropdown);
217
218 auto lightDirectionLayout = new QVBoxLayout;
219 lightDirectionLayout->addLayout(lightThetaLayout);
220 lightDirectionLayout->addSpacing(12);
221 lightDirectionLayout->addLayout(lightPhiLayout);
222
223 QGroupBox* lightAnglesGroup = new QGroupBox(tr("Light Direction"));
224 lightAnglesGroup->setLayout(lightDirectionLayout);
225
226 connect(lightTheta, &QSlider::valueChanged, this, &G4DisplayView::changeLightDirection);
227 connect(lightTheta, &QSlider::valueChanged, ltheta_LCD, qOverload<int>(&QLCDNumber::display));
228 connect(lthetaDropdown, &QComboBox::currentTextChanged,
229 this, [this](const QString&) { setLightDirection(0); });
230
231 connect(lightPhi, &QSlider::valueChanged, this, &G4DisplayView::changeLightDirection);
232 connect(lightPhi, &QSlider::valueChanged, lphi_LCD, qOverload<int>(&QLCDNumber::display));
233 connect(lphiDropdown, &QComboBox::currentTextChanged,
234 this, [this](const QString&) { setLightDirection(1); });
235
236 // ----------------------
237 // Scene properties group
238 // ----------------------
239
240 QLabel* cullingLabel = new QLabel(tr("Culling:"));
241 cullingDropdown = new QComboBox;
242 cullingDropdown->addItem(tr("Reset"));
243 cullingDropdown->addItem(tr("Covered Daughters"));
244 cullingDropdown->addItem(tr("Density: 1 mg/cm3"));
245 cullingDropdown->addItem(tr("Density: 10 mg/cm3"));
246 cullingDropdown->addItem(tr("Density: 100 mg/cm3"));
247 cullingDropdown->addItem(tr("Density: 1 g/cm3"));
248 cullingDropdown->addItem(tr("Density: 10 g/cm3"));
249
250 QLabel* backgroundColorLabel = new QLabel(tr("Background Color:"));
251 backgroundColorDropdown = new QComboBox;
252 backgroundColorDropdown->addItem(tr("lightslategray"));
253 backgroundColorDropdown->addItem(tr("ghostwhite"));
254 backgroundColorDropdown->addItem(tr("black"));
255 backgroundColorDropdown->addItem(tr("navy"));
256 backgroundColorDropdown->addItem(tr("whitesmoke"));
257 backgroundColorDropdown->addItem(tr("lightskyblue"));
258 backgroundColorDropdown->addItem(tr("deepskyblue"));
259 backgroundColorDropdown->addItem(tr("lightsteelblue"));
260 backgroundColorDropdown->addItem(tr("blueviolet"));
261 backgroundColorDropdown->addItem(tr("turquoise"));
262 backgroundColorDropdown->addItem(tr("mediumaquamarine"));
263 backgroundColorDropdown->addItem(tr("springgreen"));
264 backgroundColorDropdown->addItem(tr("lawngreen"));
265 backgroundColorDropdown->addItem(tr("yellowgreen"));
266 backgroundColorDropdown->addItem(tr("lemonchiffon"));
267 backgroundColorDropdown->addItem(tr("antiquewhite"));
268 backgroundColorDropdown->addItem(tr("wheat"));
269 backgroundColorDropdown->addItem(tr("sienna"));
270 backgroundColorDropdown->addItem(tr("snow"));
271 backgroundColorDropdown->addItem(tr("floralwhite"));
272 backgroundColorDropdown->addItem(tr("lightsalmon"));
273 backgroundColorDropdown->addItem(tr("orchid"));
274 backgroundColorDropdown->addItem(tr("plum"));
275 backgroundColorDropdown->setCurrentIndex(0);
276
277 connect(cullingDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_culling);
278 connect(backgroundColorDropdown, &QComboBox::currentTextChanged, this, &G4DisplayView::set_background);
279
280 QVBoxLayout* sceneLayout = new QVBoxLayout;
281 sceneLayout->addWidget(cullingLabel);
282 sceneLayout->addWidget(cullingDropdown);
283 sceneLayout->addSpacing(12);
284 sceneLayout->addWidget(backgroundColorLabel);
285 sceneLayout->addWidget(backgroundColorDropdown);
286
287 QGroupBox* spropertyGroup = new QGroupBox(tr("Scene Properties"));
288 spropertyGroup->setLayout(sceneLayout);
289
290 QHBoxLayout* lightAndProperties = new QHBoxLayout;
291 lightAndProperties->addWidget(lightAnglesGroup);
292 lightAndProperties->addSpacing(12);
293 lightAndProperties->addWidget(spropertyGroup);
294
295 // -------------------------
296 // Slice (cutaway) controls
297 // -------------------------
298
299 // X slice controls.
300 sliceXEdit = new QLineEdit(tr("0"));
301 sliceXEdit->setMaximumWidth(100);
302 sliceXActi = new QCheckBox(tr("&On"));
303 sliceXActi->setChecked(false);
304 sliceXInve = new QCheckBox(tr("&Flip"));
305 sliceXInve->setChecked(false);
306
307 auto sliceXLayout = new QHBoxLayout;
308 sliceXLayout->addWidget(new QLabel(tr("X: ")));
309 sliceXLayout->addWidget(sliceXEdit);
310 sliceXLayout->addStretch(1);
311 sliceXLayout->addWidget(sliceXActi);
312 sliceXLayout->addWidget(sliceXInve);
313 sliceXLayout->addStretch(1);
314
315 // Y slice controls.
316 sliceYEdit = new QLineEdit(tr("0"));
317 sliceYEdit->setMaximumWidth(100);
318 sliceYActi = new QCheckBox(tr("&On"));
319 sliceYActi->setChecked(false);
320 sliceYInve = new QCheckBox(tr("&Flip"));
321 sliceYInve->setChecked(false);
322
323 auto sliceYLayout = new QHBoxLayout;
324 sliceYLayout->addWidget(new QLabel(tr("Y: ")));
325 sliceYLayout->addWidget(sliceYEdit);
326 sliceYLayout->addStretch(1);
327 sliceYLayout->addWidget(sliceYActi);
328 sliceYLayout->addWidget(sliceYInve);
329 sliceYLayout->addStretch(1);
330
331 // Z slice controls.
332 sliceZEdit = new QLineEdit(tr("0"));
333 sliceZEdit->setMaximumWidth(100);
334 sliceZActi = new QCheckBox(tr("&On"));
335 sliceZActi->setChecked(false);
336 sliceZInve = new QCheckBox(tr("&Flip"));
337 sliceZInve->setChecked(false);
338
339 auto sliceZLayout = new QHBoxLayout;
340 sliceZLayout->addWidget(new QLabel(tr("Z: ")));
341 sliceZLayout->addWidget(sliceZEdit);
342 sliceZLayout->addStretch(1);
343 sliceZLayout->addWidget(sliceZActi);
344 sliceZLayout->addWidget(sliceZInve);
345 sliceZLayout->addStretch(1);
346
347 // Clear slice planes button.
348 QPushButton* clearSliceButton = new QPushButton(tr("Clear Slices"));
349 clearSliceButton->setToolTip("Clear Slice Planes");
350 clearSliceButton->setIcon(QIcon::fromTheme("edit-clear"));
351 clearSliceButton->setIconSize(QSize(16, 16));
352 connect(clearSliceButton, &QPushButton::clicked, this, &G4DisplayView::clearSlices);
353
354 // Slice composition mode: intersection vs union.
355 QGroupBox* sliceChoiceBox = new QGroupBox(tr("Slices Style"));
356 sliceSectn = new QRadioButton(tr("&Intersection"), sliceChoiceBox);
357 sliceUnion = new QRadioButton(tr("&Union"), sliceChoiceBox);
358 sliceSectn->setChecked(true);
359
360 connect(sliceSectn, &QRadioButton::toggled, this, &G4DisplayView::slice);
361 connect(sliceUnion, &QRadioButton::toggled, this, &G4DisplayView::slice);
362
363 auto sliceChoiceLayout = new QHBoxLayout;
364 sliceChoiceLayout->addWidget(sliceSectn);
365 sliceChoiceLayout->addWidget(sliceUnion);
366 sliceChoiceBox->setLayout(sliceChoiceLayout);
367
368 // Slices layout.
369 auto sliceLayout = new QVBoxLayout;
370 sliceLayout->addLayout(sliceXLayout);
371 sliceLayout->addLayout(sliceYLayout);
372 sliceLayout->addLayout(sliceZLayout);
373 sliceLayout->addWidget(sliceChoiceBox);
374 sliceLayout->addWidget(clearSliceButton);
375
376 // Connect slice UI signals to slice recomputation.
377 connect(sliceXEdit, &QLineEdit::returnPressed, this, &G4DisplayView::slice);
378 connect(sliceYEdit, &QLineEdit::returnPressed, this, &G4DisplayView::slice);
379 connect(sliceZEdit, &QLineEdit::returnPressed, this, &G4DisplayView::slice);
380
381#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) // Qt ≤ 6.6
382 connect(sliceXActi, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
383 connect(sliceYActi, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
384 connect(sliceZActi, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
385 connect(sliceXInve, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
386 connect(sliceYInve, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
387 connect(sliceZInve, &QCheckBox::stateChanged, this, &G4DisplayView::slice);
388#else // Qt ≥ 6.7
389 connect(sliceXActi, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
390 connect(sliceYActi, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
391 connect(sliceZActi, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
392 connect(sliceXInve, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
393 connect(sliceYInve, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
394 connect(sliceZInve, &QCheckBox::checkStateChanged, this, &G4DisplayView::slice);
395#endif
396
397 // ------------------------------
398 // Field line precision controls
399 // ------------------------------
400
401 QGroupBox* fieldPrecisionBox = new QGroupBox(tr("Number of Field Points"));
402 field_npoints = new QLineEdit(QString::number(field_NPOINTS), this);
403 field_npoints->setMaximumWidth(40);
404
405 QFont font = field_npoints->font();
406 font.setPointSize(24);
407 field_npoints->setFont(font);
408
409 connect(field_npoints, &QLineEdit::returnPressed, this, &G4DisplayView::field_precision_changed);
410
411 // Buttons + field point count UI combined.
412 auto fieldPointsHBox = new QHBoxLayout;
413 fieldPointsHBox->addWidget(field_npoints);
414 fieldPrecisionBox->setLayout(fieldPointsHBox);
415
416 auto buttons_field_HBox = new QHBoxLayout;
417 buttons_field_HBox->addWidget(buttons_set1);
418 buttons_field_HBox->addWidget(fieldPrecisionBox);
419 fieldPrecisionBox->setMaximumHeight(3 * buttons_set1->height());
420 fieldPrecisionBox->setMaximumWidth(140);
421
422 // -------------------------
423 // Assemble final tab layout
424 // -------------------------
425
426 auto mainLayout = new QVBoxLayout;
427 mainLayout->addLayout(buttons_field_HBox);
428 mainLayout->addLayout(cameraAndPerspective);
429 mainLayout->addLayout(lightAndProperties);
430 mainLayout->addLayout(sliceLayout);
431 setLayout(mainLayout);
432}
433
434void G4DisplayView::changeCameraDirection() {
435 // Construct the Geant4 command using the current slider values and send it to the UI manager.
436 string command = "/vis/viewer/set/viewpointThetaPhi " +
437 to_string(cameraTheta->value()) + " " +
438 to_string(cameraPhi->value());
439 G4UImanager::GetUIpointer()->ApplyCommand(command);
440}
441
442void G4DisplayView::setCameraDirection(int which) {
443 // Dropdown values are interpreted as degrees; command is issued first, then the slider is synced.
444 string thetaValue = thetaDropdown->currentText().toStdString();
445 string phiValue = phiDropdown->currentText().toStdString();
446
447 string command = "/vis/viewer/set/viewpointThetaPhi " + thetaValue + " " + phiValue;
448 G4UImanager::GetUIpointer()->ApplyCommand(command);
449
450 int thetaDeg = thetaDropdown->currentText().toInt();
451 int phiDeg = phiDropdown->currentText().toInt();
452
453 if (cameraTheta && which == 0) cameraTheta->setValue(thetaDeg);
454 if (cameraPhi && which == 1) cameraPhi->setValue(phiDeg);
455}
456
457void G4DisplayView::set_projection() {
458 // Dropdown selections map to Geant4 projection parameters: orthogonal (o) or perspective (p, angle).
459 string value = perspectiveDropdown->currentText().toStdString();
460
461 string g4perspective = "o";
462 string g4perpvalue = "0";
463 if (value.find("Perspective") != string::npos) {
464 g4perspective = "p";
465 // Second token after space is the angle.
466 g4perpvalue = value.substr(value.find(" ") + 1);
467 }
468
469 string command = "/vis/viewer/set/projection " + g4perspective + " " + g4perpvalue;
470 G4UImanager::GetUIpointer()->ApplyCommand(command);
471}
472
473void G4DisplayView::set_precision() {
474 // Viewer circle segmentation impacts curved primitive smoothness.
475 string value = precisionDropdown->currentText().toStdString();
476
477 string command = "/vis/viewer/set/lineSegmentsPerCircle " + value;
478 G4UImanager::GetUIpointer()->ApplyCommand(command);
479 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/flush");
480}
481
482void G4DisplayView::set_culling() {
483 // Culling configuration uses Geant4 viewer culling commands.
484 string value = cullingDropdown->currentText().toStdString();
485 int index = cullingDropdown->currentIndex() - 2;
486
487 if (value.find("Reset") != string::npos) {
488 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling global true");
489 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling density false");
490 }
491 else if (value.find("Daughters") != string::npos) {
492 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling coveredDaughters true");
493 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/set/culling density false");
494 }
495 else {
496 // Density-based thresholds (unit assumed by Geant4 command expectations).
497 vector<double> density = {0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0};
498 string command = "/vis/viewer/set/culling density true " + to_string(density[index]);
499 G4UImanager::GetUIpointer()->ApplyCommand(command);
500 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/flush");
501 log->info(0, command);
502 }
503}
504
505void G4DisplayView::set_background() {
506 // Translate CSS-like names to normalized RGB values expected by Geant4.
507 string value = backgroundColorDropdown->currentText().toStdString();
508
509 string g4value = "0 0 0";
510 if (value.find("black") != string::npos) {
511 g4value = "0.0 0.0 0.0";
512 }
513 else if (value.find("navy") != string::npos) {
514 g4value = "0.0 0.0 0.50196";
515 }
516 else if (value.find("lightslategray") != string::npos) {
517 g4value = "0.46667 0.53333 0.60000";
518 }
519 else if (value.find("whitesmoke") != string::npos) {
520 g4value = "0.96078 0.96078 0.96078";
521 }
522 else if (value.find("ghostwhite") != string::npos) {
523 g4value = "0.97255 0.97255 1.00000";
524 }
525 else if (value.find("lightskyblue") != string::npos) {
526 g4value = "0.52941 0.80784 0.98039";
527 }
528 else if (value.find("deepskyblue") != string::npos) {
529 g4value = "0.00000 0.74902 1.00000";
530 }
531 else if (value.find("lightsteelblue") != string::npos) {
532 g4value = "0.69020 0.76863 0.87059";
533 }
534 else if (value.find("blueviolet") != string::npos) {
535 g4value = "0.54118 0.16863 0.88627";
536 }
537 else if (value.find("turquoise") != string::npos) {
538 g4value = "0.25098 0.87843 0.81569";
539 }
540 else if (value.find("mediumaquamarine") != string::npos) {
541 g4value = "0.40000 0.80392 0.66667";
542 }
543 else if (value.find("springgreen") != string::npos) {
544 g4value = "0.00000 1.00000 0.49804";
545 }
546 else if (value.find("lawngreen") != string::npos) {
547 g4value = "0.48627 0.98824 0.00000";
548 }
549 else if (value.find("yellowgreen") != string::npos) {
550 g4value = "0.60392 0.80392 0.19608";
551 }
552 else if (value.find("lemonchiffon") != string::npos) {
553 g4value = "1.00000 0.98039 0.80392";
554 }
555 else if (value.find("antiquewhite") != string::npos) {
556 g4value = "0.98039 0.92157 0.84314";
557 }
558 else if (value.find("wheat") != string::npos) {
559 g4value = "0.96078 0.87059 0.70196";
560 }
561 else if (value.find("sienna") != string::npos) {
562 g4value = "0.62745 0.32157 0.17647";
563 }
564 else if (value.find("snow") != string::npos) {
565 g4value = "1.00000 0.98039 0.98039";
566 }
567 else if (value.find("floralwhite") != string::npos) {
568 g4value = "1.00000 0.98039 0.94118";
569 }
570 else if (value.find("lightsalmon") != string::npos) {
571 g4value = "1.00000 0.62745 0.47843";
572 }
573 else if (value.find("orchid") != string::npos) {
574 g4value = "0.85490 0.43922 0.83922";
575 }
576 else if (value.find("plum") != string::npos) {
577 g4value = "0.86667 0.62745 0.86667";
578 }
579 else {
580 // Fallback to white for unexpected selections.
581 g4value = "1.0 1.0 1.0";
582 }
583
584 string command = "/vis/viewer/set/background " + g4value;
585 G4UImanager::GetUIpointer()->ApplyCommand(command);
586}
587
588void G4DisplayView::changeLightDirection() {
589 // Construct the Geant4 command using the current slider values and send it to the UI manager.
590 string command = "/vis/viewer/set/lightsThetaPhi " +
591 to_string(lightTheta->value()) + " " +
592 to_string(lightPhi->value());
593 G4UImanager::GetUIpointer()->ApplyCommand(command);
594}
595
596void G4DisplayView::setLightDirection(int which) {
597 // Dropdown values are interpreted as degrees; command is issued first, then the slider is synced.
598 string thetaValue = lthetaDropdown->currentText().toStdString();
599 string phiValue = lphiDropdown->currentText().toStdString();
600
601 string command = "/vis/viewer/set/lightsThetaPhi " + thetaValue + " " + phiValue;
602 G4UImanager::GetUIpointer()->ApplyCommand(command);
603
604 int thetaDeg = lthetaDropdown->currentText().toInt();
605 int phiDeg = lphiDropdown->currentText().toInt();
606
607 if (lightTheta && which == 0) lightTheta->setValue(thetaDeg);
608 if (lightPhi && which == 1) lightPhi->setValue(phiDeg);
609}
610
611void G4DisplayView::slice() {
612 G4UImanager* g4uim = G4UImanager::GetUIpointer();
613 if (g4uim == nullptr) { return; }
614
615 // Reset existing planes before applying the newly requested slice configuration.
616 g4uim->ApplyCommand("/vis/viewer/clearCutawayPlanes");
617
618 // Select how multiple planes combine.
619 if (sliceSectn->isChecked()) { g4uim->ApplyCommand("/vis/viewer/set/cutawayMode intersection"); }
620 else if (sliceUnion->isChecked()) { g4uim->ApplyCommand("/vis/viewer/set/cutawayMode union"); }
621
622 // Clear again to ensure mode change does not retain previously-defined planes.
623 g4uim->ApplyCommand("/vis/viewer/clearCutawayPlanes");
624
625 // For each enabled axis, add a plane at the requested position. Values are interpreted as mm.
626 if (sliceXActi->isChecked()) {
627 string command = "/vis/viewer/addCutawayPlane " + sliceXEdit->text().toStdString() + " 0 0 mm " +
628 to_string(sliceXInve->isChecked() ? -1 : 1) + " 0 0 ";
629 cout << "X " << command << endl;
630 g4uim->ApplyCommand(command);
631 }
632
633 if (sliceYActi->isChecked()) {
634 string command = "/vis/viewer/addCutawayPlane 0 " + sliceYEdit->text().toStdString() + " 0 mm 0 " +
635 to_string(sliceYInve->isChecked() ? -1 : 1) + " 0 ";
636 cout << "Y " << command << endl;
637 g4uim->ApplyCommand(command);
638 }
639
640 if (sliceZActi->isChecked()) {
641 string command = "/vis/viewer/addCutawayPlane 0 0 " + sliceZEdit->text().toStdString() + " mm 0 0 " +
642 to_string(sliceZInve->isChecked() ? -1 : 1);
643 cout << "Z " << command << endl;
644 g4uim->ApplyCommand(command);
645 }
646}
647
648void G4DisplayView::clearSlices() {
649 // Clear cutaway planes in the viewer and reset activation UI state.
650 G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/clearCutawayPlanes");
651
652 // NOTE: Only X is reset here in the current implementation; Y/Z are left unchanged.
653 // This behavior is preserved intentionally (no logic changes).
654 sliceXActi->setChecked(false);
655}
656
657void G4DisplayView::apply_buttons_set1(int index) {
658 G4UImanager* g4uim = G4UImanager::GetUIpointer();
659 if (g4uim == nullptr) { return; }
660
661 bool button_state = buttons_set1->lastButtonState();
662
663 if (index == 0) {
664 // Hidden edges on/off.
665 string command = string("/vis/viewer/set/hiddenEdge") + (button_state ? " 1" : " 0");
666 g4uim->ApplyCommand(command);
667 g4uim->ApplyCommand("/vis/viewer/flush");
668 }
669 else if (index == 1) {
670 // Anti-aliasing: handled via OpenGL state for viewers where it applies.
671 if (button_state == 0) {
672 glDisable(GL_LINE_SMOOTH);
673 glDisable(GL_POLYGON_SMOOTH);
674 }
675 else {
676 glEnable(GL_LINE_SMOOTH);
677 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
678 glEnable(GL_POLYGON_SMOOTH);
679 glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
680 }
681 }
682 else if (index == 2) {
683 // Auxiliary edges typically implies hidden edges; keep UI state coherent.
684 string command = string("/vis/viewer/set/auxiliaryEdge") + (button_state ? " 1" : " 0");
685 g4uim->ApplyCommand(command);
686
687 command = string("/vis/viewer/set/hiddenEdge") + (button_state ? " 1" : " 0");
688 g4uim->ApplyCommand(command);
689
690 if (buttons_set1->buttonStatus(0) != button_state) { buttons_set1->toggleButton(0); }
691 }
692 else if (index == 3) {
693 // Magnetic field line visualization model.
694 if (button_state == 0) {
695 string command = string("/vis/scene/activateModel Field 0");
696 g4uim->ApplyCommand(command);
697 g4uim->ApplyCommand("/vis/scene/removeModel Field");
698 }
699 else {
700 string npoints = to_string(field_NPOINTS);
701 string command = string("/vis/scene/add/magneticField ") + npoints;
702 g4uim->ApplyCommand(command);
703 }
704 }
705 else if (index == 4) {
706 // Axes visualization (added when enabled).
707 if (button_state == 1) {
708 string command = string("/vis/scene/add/axes");
709 g4uim->ApplyCommand(command);
710 }
711 }
712 else if (index == 5) {
713 // Scale visualization (added when enabled).
714 if (button_state == 1) {
715 string command = string("/vis/scene/add/scale");
716 g4uim->ApplyCommand(command);
717 }
718 }
719}
720
721void G4DisplayView::field_precision_changed() {
722 G4UImanager* g4uim = G4UImanager::GetUIpointer();
723 if (g4uim == nullptr) { return; }
724
725 // Parse the updated point count from the UI.
726 field_NPOINTS = field_npoints->text().toInt();
727
728 // If field lines are currently active, re-issue the field model to apply the new point count.
729 if (buttons_set1->buttonStatus(3) == 1) {
730 string command = string("vis/scene/activateModel Field 0");
731 g4uim->ApplyCommand(command);
732 g4uim->ApplyCommand("/vis/scene/removeModel Field");
733
734 string npoints = to_string(field_NPOINTS);
735 command = string("/vis/scene/add/magneticField ") + npoints;
736 G4UImanager::GetUIpointer()->ApplyCommand(command);
737 }
738}
G4DisplayView(const std::shared_ptr< GOptions > &gopts, std::shared_ptr< GLogger > logger, QWidget *parent=nullptr)
Construct the view-control tab.
void debug(debug_type type, Args &&... args) const
void info(int level, Args &&... args) const
void toggleButton(int index)
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.
CONSTRUCTOR
G4Light getG4Light(const std::shared_ptr< GOptions > &gopts)
Read the g4light option node and return a projected G4Light 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.