A ground-up replacement for epaswmm5.exe
SWMMVis is a Qt6/C++20 GIS-based interface for the OpenSWMM 6.0 engine. It is not a port of the Delphi GUI and it is not a wrapper around it — it is a second GUI living in the same git repository as the first, which is still sitting on master exactly as EPA left it.
The fork chain
Two GUIs, one repository
Commit 2 of this repo imported the EPA Delphi source. Everything after it is the replacement. Both are still here — you can check out either one.
What it does
Where the code is
30 buckets — 28 module directories under src/ with matching headers, one header-only module, and the handful of files at the root of src/. Bar length is lines of code, source plus headers.
Architecture
Three ideas carry most of the design: the engine holds the model, layers stack in a canvas that renders through two interchangeable paths, and every edit is an undo command.
1. The engine is the model
There is no parallel network object graph in the GUI. When the Simulation Options dialog opens, it reads every option back out of the engine through swmm_options_get rather than trusting a cached copy. When a property panel shows a node, it asks the engine what kind of node it is. The GUI keeps render-side caches — geometry structs, k-d trees, LOD pyramids — but those are derived, and they carry explicit rebuild flags.
The clearest evidence is a file that exists only to police this: include/ui/swmmvis_hydration_audit.h. It is a table of status-bar widgets paired with the engine option key each one reflects, and a regression test walks it asserting agreement at three trigger points — project open, tab activation, external mutation. Add a status-bar widget that shows an [OPTIONS] value, add a row, and the test picks it up automatically.
2. The layer stack
Everything on the canvas is a layer with a common base (OpenSWMMVisLayer). Bottom to top: basemaps and GIS data, then the 2D mesh, then 2D results, then the 1D network, then annotations.
3. Two render paths that must never both paint
Each heavy layer has a Qt Scene Graph renderer (GPU) and a QPainter painter (CPU). qsgOwnsRendering() gates the painter so the two pipelines cannot double-paint. The fallback is not dead code — a masked mesh, or a 2D results layer forced onto the CPU, deliberately drags the mesh back to QPainter, because if one layer went to the GPU and the layer above it did not, the stacking order would invert.
4. Undo is the edit API
29 command classes on a MapUndoStack with a configurable depth. Note the two at the end — inserting and fusing a virtual junction are undoable map operations, not a text edit in the .inp.
Module map
The engine binding
The GUI talks to openswmm.engine through a plain C API. This is the widest part of the surface and the part most likely to break when the engine moves.
Headers consumed
Bar length is include-site count across src/ and include/.
The run lifecycle
SimulationRunner uses the full nine-call lifecycle rather than the one-shot swmm_run, specifically so progress and warning callbacks can be registered. The step loop runs on a QtConcurrent worker; the static C callbacks post back to the GUI thread through QMetaObject::invokeMethod with a queued connection.
It can still drive a 5.x engine
SimulationRunner branches on an engineVersion string that travels per-session in the .oswp: the legacy worker for 5.x, the refactored engine for 6.0.0+. One project can pin one engine while another pins a different one.
// src/simulation/simulationrunner.cpp:267 // Use legacy worker for 5.x versions, refactored engine for 6.0.0+
FV solver options — a live drift check
Both trees were cloned on the same day. The dialog names 20 FV_ keys; the engine's OptionsHandler.cpp parses 22. Every key the GUI names is real — the drift is one-directional.
Component catalogue
361 components across 14 groups, with the doxygen brief pulled straight from the header and the line count summing header plus implementation. Search matches the name and the description.
2D meshing and the million-cell problem
The largest single engineering effort in the branch. Generating a mesh is one problem; drawing nine million triangles without freezing the window is a different one.
The generation pipeline
Click a stage to read what it does and what went wrong in it.
Triangle is extended, never forked
vendor/triangle/ holds exactly two files — triangle.c and triangle.h — and they are untouched. Cancellation, progress reporting and a graded size function all ride on Triangle's documented triunsuitable() / EXTERNAL_TEST extension point, left undefined in the library target and resolved at the final link.
Cancellation is the interesting one. Refinement was previously uninterruptible, so the Stop button was dead for its whole duration. The fix is not a longjmp — that would leak the mesh pools, the dominant allocation in the pipeline at roughly 200 bytes per output vertex. Instead, once cancelled, the hook reports every triangle as suitable, the bad-triangle queue drains naturally, and triangulate() unwinds through its own triangledeinit().
Measured load performance
From the alpha.3 release notes. These are the numbers that justify the scene-graph work.
The 65,532 chunk
Qt's batch renderer addresses batched vertices with 16-bit indices, so a single geometry node above 65,535 vertices wraps and draws garbage. Uploads now split at 65,532 — the largest value under 216−1 divisible by both 3 (bare triangles) and 6 (thick-segment quads), so a chunk boundary can never fall inside a primitive.
2D sections in the .inp
What makes it a SWMM 6 GUI
Most of the interface would be recognisable to anyone who has used the Delphi GUI. These are the parts that would not be.
Virtual junctions are a first-class map object
A virtual junction is SWMM6's way of letting a chain of conduits be modelled as one hydraulic reach: the engine concatenates them and the junction becomes an ordinary interior face. In this GUI that is not a hand edit to a [VIRTUAL_JUNCTIONS] section — it is a map tool, a property adapter, its own symbol, two undo commands, and a profile view that draws the node as pipe rather than as a manhole with an editable ground depth.
The engine error family, translated
The engine rejects an ineligible virtual junction with a numeric code. The GUI carries plain-English text for all seven — three more than the four usually quoted in conversion notes.
The finite-volume solver, exposed as a dialog
FLOW_ROUTING FV appears in the routing combo beside Steady, Kinematic and Dynamic Wave, and the dialog grows two contextual groups when it is chosen. The dialog detects FV support by asking the engine for an FV key and seeing whether anything comes back — a build that predates the solver answers with an empty string and the groups stay disabled.
// src/ui/dialogs/simulationoptionsdialog.cpp:175
const bool fvSupported = !legacy && !getOption("FV_CFL").isEmpty();
Full key-by-key coverage is on the Engine binding tab.
Other SWMM 6 surface
Building it
Three sibling checkouts, vcpkg for the rest, and a CMake preset per platform. The awkward part is that two dependencies are sibling directories rather than package-manager entries.
Dependencies
The sibling layout
The engine is expected at ../openswmm.engine. Clone it as a sibling, not a submodule — the only submodule in the repo is the doxygen theme.
Configure and build
CI
Tests
Three tiers. tests/unit is pure logic with no Qt widgets. tests/gui is the bulk — widget and renderer behaviour, including an accessibility sweep and a theme-contrast test. tests/verification is different in kind: six checks that write a .out.txt transcript beside themselves, so the numbers are reviewable rather than merely green.
Findings
Everything below was checked against the cloned trees on the read date, not recalled. Each card names how it was verified. Where a number came from the project's own release notes rather than from a measurement I ran, the card says so.
Development history
278 commits. The 2023 cluster is the Delphi import; the real work is 2024 and then a sustained run from April 2026.
Who wrote it
Built in named slices
1,473 references to a “Slice” identifier across 203 distinct slices, citing ten planning documents by section number — GUI_IMPLEMENTATION_PLAN.md (27 references), RENDERING_RULE_MODEL_PLAN.md (25), RENDERING_OUTPUT_SUBLAYERS_PLAN.md (16) and seven more.