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