Skip to content

Commit 36d83f7

Browse files
committed
field line options and examples
1 parent e926cb1 commit 36d83f7

4 files changed

Lines changed: 124 additions & 9 deletions

File tree

gemc/g4display/g4display_options.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,15 @@ GOptions defineOptions() {
196196

197197
goptions.defineOption("g4decoration", "Adds optional Geant4 scene decorations", g4decoration, help);
198198

199+
help = "Display magnetic field lines in the initial visualization scene. \n \n";
200+
help += "The value is the number of field-line integration points. Set to 0 to disable. \n \n";
201+
help += "Example: -show_field_lines=100 \n";
202+
goptions.defineOption(GVariable("show_field_lines", 0, "number of field-line points to display"), help);
203+
204+
help = "Display auxiliary edges in the initial visualization scene. \n \n";
205+
help += "Example: -show_auxiliary_edges=true \n";
206+
goptions.defineOption(GVariable("show_auxiliary_edges", false, "show auxiliary volume edges"), help);
207+
199208
// scenetext
200209
goptions.addGOptions(addSceneTextsOptions());
201210

gemc/g4display/tabs/g4displayview.cc

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ G4DisplayView::G4DisplayView(const std::shared_ptr<GOptions>& gopts,
447447
// X slice controls.
448448
sliceXEdit = new QLineEdit(tr("0"));
449449
sliceXEdit->setMaximumWidth(100);
450+
sliceXEdit->setValidator(new QDoubleValidator(sliceXEdit));
450451
sliceXActi = new QCheckBox(tr("&On"));
451452
sliceXActi->setChecked(false);
452453
sliceXInve = new QCheckBox(tr("&Flip"));
@@ -465,6 +466,7 @@ G4DisplayView::G4DisplayView(const std::shared_ptr<GOptions>& gopts,
465466
// Y slice controls.
466467
sliceYEdit = new QLineEdit(tr("0"));
467468
sliceYEdit->setMaximumWidth(100);
469+
sliceYEdit->setValidator(new QDoubleValidator(sliceYEdit));
468470
sliceYActi = new QCheckBox(tr("&On"));
469471
sliceYActi->setChecked(false);
470472
sliceYInve = new QCheckBox(tr("&Flip"));
@@ -483,6 +485,7 @@ G4DisplayView::G4DisplayView(const std::shared_ptr<GOptions>& gopts,
483485
// Z slice controls.
484486
sliceZEdit = new QLineEdit(tr("0"));
485487
sliceZEdit->setMaximumWidth(100);
488+
sliceZEdit->setValidator(new QDoubleValidator(sliceZEdit));
486489
sliceZActi = new QCheckBox(tr("&On"));
487490
sliceZActi->setChecked(false);
488491
sliceZInve = new QCheckBox(tr("&Flip"));
@@ -801,39 +804,45 @@ void G4DisplayView::slice() {
801804
if (sliceSectn->isChecked()) { g4uim->ApplyCommand("/vis/viewer/set/cutawayMode intersection"); }
802805
else if (sliceUnion->isChecked()) { g4uim->ApplyCommand("/vis/viewer/set/cutawayMode union"); }
803806

804-
// Clear again to ensure mode change does not retain previously-defined planes.
805-
g4uim->ApplyCommand("/vis/viewer/clearCutawayPlanes");
806-
807807
// For each enabled axis, add a plane at the requested position. Values are interpreted as mm.
808808
if (sliceXActi->isChecked()) {
809809
string command = "/vis/viewer/addCutawayPlane " + sliceXEdit->text().toStdString() + " 0 0 mm " +
810810
to_string(sliceXInve->isChecked() ? -1 : 1) + " 0 0 ";
811-
cout << "X " << command << endl;
812811
g4uim->ApplyCommand(command);
813812
}
814813

815814
if (sliceYActi->isChecked()) {
816815
string command = "/vis/viewer/addCutawayPlane 0 " + sliceYEdit->text().toStdString() + " 0 mm 0 " +
817816
to_string(sliceYInve->isChecked() ? -1 : 1) + " 0 ";
818-
cout << "Y " << command << endl;
819817
g4uim->ApplyCommand(command);
820818
}
821819

822820
if (sliceZActi->isChecked()) {
823821
string command = "/vis/viewer/addCutawayPlane 0 0 " + sliceZEdit->text().toStdString() + " mm 0 0 " +
824822
to_string(sliceZInve->isChecked() ? -1 : 1);
825-
cout << "Z " << command << endl;
826823
g4uim->ApplyCommand(command);
827824
}
825+
826+
g4uim->ApplyCommand("/vis/viewer/rebuild");
827+
g4uim->ApplyCommand("/vis/viewer/flush");
828828
}
829829

830830
void G4DisplayView::clearSlices() {
831831
// Clear cutaway planes in the viewer and reset activation UI state.
832-
G4UImanager::GetUIpointer()->ApplyCommand("/vis/viewer/clearCutawayPlanes");
832+
G4UImanager* g4uim = G4UImanager::GetUIpointer();
833+
if (g4uim == nullptr) { return; }
834+
835+
QSignalBlocker blockX(sliceXActi);
836+
QSignalBlocker blockY(sliceYActi);
837+
QSignalBlocker blockZ(sliceZActi);
833838

834-
// NOTE: Only X is reset here in the current implementation; Y/Z are left unchanged.
835-
// This behavior is preserved intentionally (no logic changes).
836839
sliceXActi->setChecked(false);
840+
sliceYActi->setChecked(false);
841+
sliceZActi->setChecked(false);
842+
843+
g4uim->ApplyCommand("/vis/viewer/clearCutawayPlanes");
844+
g4uim->ApplyCommand("/vis/viewer/rebuild");
845+
g4uim->ApplyCommand("/vis/viewer/flush");
837846
}
838847

839848
void G4DisplayView::apply_buttons_set1(int index) {

gemc/utilities/gemcUtilities.cc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,57 @@
1717
#include "g4SceneProperties.h"
1818
#include "g4display_options.h"
1919

20+
#include <algorithm>
21+
#include <cctype>
22+
#include <cstdlib>
23+
#include <iostream>
24+
#include <sstream>
25+
26+
namespace {
27+
bool scalarOptionIsTrue(const std::shared_ptr<GOptions>& gopts, const std::string& name) {
28+
if (gopts == nullptr) { return false; }
29+
30+
std::string value = gopts->getScalarString(name);
31+
std::transform(value.begin(), value.end(), value.begin(),
32+
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
33+
34+
if (value == "true" || value == "yes" || value == "on" || value == "1") {
35+
return true;
36+
}
37+
if (value == "false" || value == "no" || value == "off" || value == "0" || value == "null") {
38+
return false;
39+
}
40+
41+
std::cerr << FATALERRORL << "The option " << name
42+
<< " accepts only true/false/yes/no/on/off/1/0." << std::endl;
43+
std::exit(EC__NOOPTIONFOUND);
44+
}
45+
46+
std::string rootExtentForFieldCommand(const std::shared_ptr<GOptions>& gopts) {
47+
if (gopts == nullptr) { return ""; }
48+
49+
std::string rootDefinition = gopts->getScalarString("root");
50+
for (auto& c : rootDefinition) {
51+
if (c == ',') { c = ' '; }
52+
}
53+
54+
const auto tokens = gutilities::getStringVectorFromString(rootDefinition);
55+
if (tokens.size() < 5 || tokens[0] != "G4Box") { return ""; }
56+
57+
const double dx = gutilities::getG4Number(tokens[1]);
58+
const double dy = gutilities::getG4Number(tokens[2]);
59+
const double dz = gutilities::getG4Number(tokens[3]);
60+
if (dx <= 0 || dy <= 0 || dz <= 0) { return ""; }
61+
62+
std::ostringstream command;
63+
command << "/vis/set/extentForField "
64+
<< -dx << " " << dx << " "
65+
<< -dy << " " << dy << " "
66+
<< -dz << " " << dz << " mm";
67+
return command.str();
68+
}
69+
}
70+
2071
namespace gemc {
2172
// return the number of cores from options.
2273
// if 0 is given, returns max number of available cores
@@ -154,6 +205,19 @@ namespace gemc {
154205
for (const auto& command : g4SceneProperties.addSceneTexts(gopts)) { cmds.emplace_back(command); }
155206
if (decorations.eventID) { cmds.emplace_back("/vis/scene/add/eventID"); }
156207

208+
if (scalarOptionIsTrue(gopts, "show_auxiliary_edges")) {
209+
cmds.emplace_back("/vis/viewer/set/auxiliaryEdge 1");
210+
cmds.emplace_back("/vis/viewer/set/hiddenEdge 1");
211+
}
212+
213+
const int fieldLinePoints = gopts->getScalarInt("show_field_lines");
214+
if (fieldLinePoints > 0) {
215+
if (const auto extent = rootExtentForFieldCommand(gopts); !extent.empty()) {
216+
cmds.emplace_back(extent);
217+
}
218+
cmds.emplace_back("/vis/scene/add/magneticField " + std::to_string(fieldLinePoints));
219+
}
220+
157221
// do not draw volumes in batch screenshot
158222
if (g4view.driver != "TOOLSSG_OFFSCREEN") {
159223
// Re-enable refresh and flush once configuration is complete.

releases/0.4.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ This version includes:
88
are no longer loaded.
99
- A new `asciimap` field plugin (`type: asciimap`) that loads ASCII field maps from data-only files
1010
with the grid defined in YAML — the GEMC3 successor of the clas12 `asciiField`.
11+
- Field-only runs now initialize against the default ROOT world when no detector system is selected.
12+
- New source examples under `examples/fields/` for torus, dipole, solenoid, and constant fields.
13+
- New visualization startup options, `-show_field_lines=<n>` and `-show_auxiliary_edges=<bool>`.
1114
- Field-line visualization now derives the sampling extent from the configured ROOT `G4Box` before
1215
adding Geant4's magnetic-field model.
1316
- A `-log_every=N[-NTH]` option for periodic per-event progress logging with the running average rate.
@@ -55,6 +58,14 @@ This version includes:
5558
the `dipole-{x,y,z}`, `cylindrical-{x,y,z}`, `phi-segmented`, and `cartesian_3D[_quadrant]`
5659
symmetries, `linear`/`none` interpolation, a `scale` factor, and `vx,vy,vz` / `rx,ry,rz` map
5760
placement. The map file is resolved next to its YAML definition, so a plain `.yaml` runs directly.
61+
- Field-only configurations can now start without selecting a detector system. GEMC treats configured
62+
field definitions as startup geometry, attaches a single configured field to ROOT when appropriate,
63+
and adds a visible viewer volume for the default ROOT world so `-gui` has an extent to frame.
64+
- Added `-show_field_lines=<n>` to add Geant4 magnetic-field lines during visualization startup. A
65+
value of `0` disables the model; positive values are passed to `/vis/scene/add/magneticField`.
66+
- Added `-show_auxiliary_edges=<bool>` to enable auxiliary volume edges at visualization startup. The
67+
option accepts `true/false/yes/no/on/off/1/0` and also enables hidden edges, matching the GUI button
68+
behavior.
5869

5970
<br/>
6071

@@ -74,6 +85,11 @@ This version includes:
7485
- Added `asciimap` examples under the gfields module: `asciimap_dipole.yaml` (dipole-z), and the
7586
clas12 `solenoid.yaml` (cylindrical-z) and `torus.yaml` (phi-segmented) maps translated from their
7687
legacy `<mfield>` headers, each with a small but complete data-only map file.
88+
- Added a top-level `examples/fields/` category with runnable torus, dipole, solenoid, and constant
89+
field display examples. The torus and solenoid examples use ASCII field maps, and each example has
90+
a simple enclosing solid chosen to make the field region visible in the Geant4 viewer.
91+
- Added `show_field_lines` and `show_auxiliary_edges` entries to the example YAML files. General
92+
examples leave them disabled, while the field-display examples enable them by default.
7793

7894
<br/>
7995

@@ -133,6 +149,9 @@ Both x86_64 and ARM64 platforms are supported.
133149
or by `-global_field`), instead of every configured `gmultipoles`/`gfields` entry. A field that no
134150
volume references — including any reset via `-no_field` — has its plugin and map skipped. The
135151
`-fieldAt` / `-fieldMapPoints` query path is unaffected and still loads all configured fields.
152+
- Field-only GUI runs no longer require selecting a detector system first. If exactly one configured
153+
field is present and no explicit `global_field` is set, GEMC attaches that field to the default
154+
ROOT world for visualization startup.
136155

137156
<br/>
138157

@@ -193,3 +212,17 @@ Both x86_64 and ARM64 platforms are supported.
193212
the YAML that defined the field.
194213
- The gfields example driver now probes every configured field by name (`GMagneto::getFieldNames`)
195214
instead of a hardcoded `dipole`, so it works for the multipole, solenoid, and torus examples alike.
215+
- Startup now proceeds for field-only configurations even when no detector system is selected. The
216+
detector construction can auto-attach a single configured field to ROOT when no explicit
217+
`global_field` is set, and the default ROOT-only scene includes a visible viewer box so Geant4 has
218+
an extent for camera setup.
219+
- Added `examples/fields/constant`, `examples/fields/dipole`, `examples/fields/solenoid`, and
220+
`examples/fields/torus`. The examples cover constant and multipole fields plus solenoid and torus
221+
ASCII field maps, each with a simple visible enclosing solid suited to its field shape.
222+
- Added the scalar display option `show_field_lines`, available as `-show_field_lines=<n>` and from
223+
YAML. Positive values emit `/vis/scene/add/magneticField <n>` after setting the field extent from
224+
the configured ROOT `G4Box`; `0` keeps field lines disabled.
225+
- Added the scalar display option `show_auxiliary_edges`, available as `-show_auxiliary_edges=<bool>`
226+
and from YAML. When enabled, startup emits `/vis/viewer/set/auxiliaryEdge 1` and
227+
`/vis/viewer/set/hiddenEdge 1`, preserving the same paired behavior as the GUI auxiliary-edges
228+
toggle.

0 commit comments

Comments
 (0)