A pure-Python toolkit for programmatically reading, writing, and manipulating Autodesk InfoDrainage .iddx project files and their SWMM simulation results. Zero required dependencies.
pip install .This installs the iddx-core package and the iddx command-line tool. The core library has zero external dependencies — it uses only Python's built-in xml.etree.ElementTree and struct.
Install extras for additional features:
pip install ".[plotting]" # matplotlib for charts and graphs
pip install ".[simulation]" # pyswmm for running SWMM simulations
pip install ".[network]" # networkx for graph-based network analysis
pip install ".[all]" # everythinggit clone https://github.com/jcholly/InfoDrainage-Python-Package.git
cd InfoDrainage-Python-Package
pip install -e .Copy the iddx_core/ folder into your project directory. That's the entire package — zero dependencies.
- Python 3.10+
- No
pip installneeded for the core library — it uses only Python built-ins
python -c "from iddx_core import IddxModel; print('iddx_core is ready')"Or with the CLI:
iddx --versioniddx_core is designed for parsing .iddx and .out files from trusted sources (your own InfoDrainage projects, files from collaborators, files generated by simulation runs you control).
The library hardens the binary .out parser against malformed and adversarial inputs (file-size caps, count/offset validation, string-length bounds), so a corrupt or malicious result file will raise ResultsError rather than hang or exhaust memory. CSV exports are also escaped against formula injection.
The XML parser uses Python's standard xml.etree.ElementTree, which since Python 3.7.1 disables external entity resolution (no XXE / file-disclosure / SSRF). Entity-expansion DoS (the "billion laughs" pattern) is still theoretically possible from a hostile .iddx — if you parse .iddx files from untrusted sources, install defusedxml and replace ET.parse calls in the library, or sandbox the process.
After installing with pip install ., the iddx command is available system-wide.
Print a full inventory of any .iddx file, including phases, rainfall sources, and available SWMM result files.
iddx summary project.iddxExport a pipe schedule for a phase, either to the console or to CSV.
iddx pipes project.iddx # print to console (first phase)
iddx pipes project.iddx --phase "Proposed" # specific phase
iddx pipes project.iddx --csv pipe_schedule.csv # export to CSVCompare peak flows, flooding, and velocities across all scenarios and return periods. Reads the SWMM .out binary files generated by InfoDrainage analysis.
iddx compare project.iddx # print comparison table
iddx compare project.iddx --csv results.csv # export to CSVRun automated design checks against the model definition and SWMM simulation results.
Model checks (no results needed):
- Cover depth violations (invert above cover, cover less than pipe diameter)
- Adverse or zero slopes
- Invalid or unusual Manning's n values
- Junction depth errors
- Broken connectivity (GUIDs referencing missing nodes)
- Missing outfalls
- Zero-area catchments
SWMM results checks (requires analysis to have been run):
- Node flooding detected in simulation output
- Link surcharging (capacity ratio > 1.0)
- Low velocity — self-cleansing risk (< 0.6 m/s)
- High velocity — erosion risk (> 6.0 m/s)
iddx validate project.iddx # validate first phase
iddx validate project.iddx --phase "Proposed" # specific phaseExit code 1 if any errors are found, 0 if only warnings or clean.
iddx --version # print version
iddx -v summary ... # verbose/debug loggingfrom iddx_core import IddxModel
model = IddxModel.open(r"C:\path\to\project.iddx")
for label, phase in model.phases.items():
s = phase.summary()
print(f"{label}: {s['catchments']} catchments, {s['junctions']} junctions, {s['connections']} pipes")from iddx_core import IddxModel
model = IddxModel.open(r"C:\path\to\project.iddx")
phase = model.phases["Proposed"]
for catchment in phase.catchments:
catchment.cv = 0.85
model.save(r"C:\path\to\updated.iddx")Open updated.iddx in InfoDrainage — every catchment now shows CV = 0.85.
from iddx_core import IddxModel
model = IddxModel.open(r"C:\path\to\project.iddx")
for pimp in [40, 60, 80, 95]:
new_phase = model.clone_phase("Proposed", f"PIMP {pimp}%")
for c in new_phase.catchments:
c.pimp = pimp
model.save(r"C:\path\to\scenarios.iddx")This creates 4 new phases, each with a different percent impervious. Open in InfoDrainage and run all scenarios at once.
The examples/ folder contains ready-to-run scripts. Each one accepts a file path as an argument, or uses a built-in default.
| Script | What it does |
|---|---|
01_model_summary.py |
Print a full inventory of any .iddx file |
02_pipe_schedule_csv.py |
Export a pipe schedule to CSV |
03_bulk_update_cv.py |
Change the runoff coefficient on every catchment |
04_sensitivity_study.py |
Generate 10 scenario phases varying CV, PIMP, and area |
05_cover_depth_check.py |
QA check: flag pipes with less than X feet of cover |
06_create_model.py |
Build a complete model from scratch |
07_compare_phases.py |
Side-by-side comparison of catchment data across phases |
08_read_results.py |
Read SWMM simulation results: peak flows, depths, flooding |
09_demo4_scenarios.py |
Generate 30 scenarios varying orifice and runoff parameters |
10_pond_depth_graph.py |
Pond depth time series graph |
10_compare_scenario_results.py |
Compare SWMM results across scenarios and export CSV |
Run an example:
cd InfoDrainage-Python-Package
python examples/01_model_summary.py "C:\path\to\project.iddx"| Use case | How |
|---|---|
| Batch modify catchments | Loop over phase.catchments, change .cv, .pimp, .area, save |
| Generate sensitivity studies | model.clone_phase() with different parameters per scenario |
| Export pipe schedules | iddx pipes project.iddx --csv or loop over phase.connections |
| QA / design validation | iddx validate project.iddx or custom checks in Python |
| Build models programmatically | IddxModel.new() + add catchments, junctions, pipes from data |
| Compare design iterations | Read multiple .iddx files and compare element counts/properties |
| Read SWMM results | ResultsReader parses .out files for peak flows, depths, flooding |
| Compare storms | iddx compare or load_results() to compare peaks across return periods |
| Read inlet HEC-22 config | Access inlet.hec22_config.gutter, .combo, .grate for full sizing params |
| Edit inlet sizing | Modify gutter slope, grate dimensions, clogging, depression and save back |
| Audit bypass connections | Filter phase.connections with .is_bypass, inspect cross-sections |
| Automate bypass routing | Create CustomCon bypass connections between inlets programmatically |
| Class | Description |
|---|---|
IddxModel |
Top-level model. IddxModel.open() to read, .save() to write, .new() to create from scratch. Supports with for context-manager use. |
Phase |
A design scenario containing all network elements. Access via model.phases["name"]. find_*(label) returns None if missing; get_*(label) raises ElementNotFoundError. |
Catchment |
Inflow area. Key properties: .label, .area, .cv (alias .runoff_coefficient), .pimp (alias .percent_impervious), .runoff_method |
Junction |
Manhole, inlet, or outfall. Key properties: .label, .cover_level, .invert_level, .is_outfall |
DrainageSystem |
Stormwater control (pond, tank, swale, etc.). Key properties: .label, .system_type, .depth |
Connection |
Pipe, channel, or bypass. Key properties: .label, .diameter, .length, .mannings_n, .is_bypass |
Hec22InletConfig |
HEC-22 inlet sizing inputs. Key properties: .hec22_inlet_type, .gutter, .grate, .combo, .curb, .slotted |
CrossSectionDetails |
Custom cross-section for bypass connections. Key properties: .points, .con_covered |
RationalResults |
Rational-method design results on a connection (read-only). Key properties: .flow, .velocity, .capacity |
UpstreamTotals |
Accumulated upstream area/flow totals on a connection (read-only). Key properties: .area, .contributing_area |
RainfallSource |
Rainfall data (NOAA, FEH, etc.). Key properties: .label, .return_periods |
ResultsReader |
Read SWMM simulation results from .out files. Key methods: .node_summary(), .link_summary(), .node_time_series(), .link_time_series() |
| Exception | When raised |
|---|---|
IddxError |
Base exception for all iddx_core errors |
IddxParseError |
.iddx file cannot be parsed (missing, corrupt, invalid XML) |
IddxValidationError |
Model data fails a validation check |
ResultsError |
SWMM .out file cannot be read (missing, corrupt, wrong format) |
ElementNotFoundError |
Requested element (junction, catchment, link, phase) not found |
from iddx_core import IddxModel, IddxParseError, ResultsError
try:
model = IddxModel.open("project.iddx")
except IddxParseError as e:
print(f"Cannot open model: {e}")
try:
results = ResultsReader("missing.out")
except ResultsError as e:
print(f"Cannot read results: {e}")| Enum | Values |
|---|---|
RunoffMethod |
RATIONAL, SCS_CURVE_NUMBER, SWMM, STATIC, FOUL, and others |
DrainageSystemType |
POND, SWALE, BIORETENTION, POROUS_PAVEMENT, CHAMBER, TANK |
ConnectionType |
CIRCULAR_PIPE, BOX_CULVERT, TRAPEZOIDAL_CHANNEL, TRIANGULAR_CHANNEL, CUSTOM_BYPASS |
Hec22InletType |
GRATE, CURB, COMBINATION, SLOTTED |
InletCapacityType |
NONE, LOW_HIGH_FLOW, RATED_BY_FLOW, HEC_22 |
InletLocation |
ON_GRADE, IN_SAG |
OutletType |
FLOW_CONTROL, ORIFICE, WEIR, COMPLEX, PUMP, FREE_OUTLET |
from iddx_core import IddxModel, Catchment, Junction, Connection
# Open
model = IddxModel.open("project.iddx")
# Access phases
phase = model.phases["Proposed"]
print(phase.summary())
# Find elements by label
j = phase.find_junction("MH-1")
c = phase.find_catchment("Site-A")
# Modify
c.cv = 0.90
c.pimp = 80
# Clone a phase
new_phase = model.clone_phase("Proposed", "High Density")
# Add new elements
phase.add_junction(Junction(label="MH-NEW", x=100, y=200, cover_level=250, invert_level=247))
# Access rainfall
for rs in model.rainfall_sources:
storm = rs.get_storm(100.0) # 100-year storm
if storm:
print(f"100-yr depth: {storm.total_depth:.2f}")
# Save
model.save("updated.iddx")After running analysis in InfoDrainage, results are saved as SWMM .out binary files in a subfolder next to the .iddx file.
from iddx_core import IddxModel, ResultsReader, find_results, load_results, build_label_map
# Find all SWMM result files for a project
result_files = find_results(r"C:\path\to\project.iddx")
for phase, files in result_files.items():
print(f"{phase}: {len(files)} storms analyzed")
# Load a single SWMM result file
results = ResultsReader(r"C:\path\to\project\Proposed_100.000_1440.00.out")
print(f"Nodes: {len(results.node_ids)}, Links: {len(results.link_ids)}")
print(f"Periods: {results.num_periods}, Interval: {results.report_interval_seconds}s")
# Get peak results for all links (cross-reference with model for labels)
model = IddxModel.open(r"C:\path\to\project.iddx")
label_map = build_label_map(model)
for ls in results.all_link_summaries(label_map):
if ls.peak_flow > 0.001:
print(f"{ls.label}: peak flow = {ls.peak_flow:.4f}")
# Get a full time series from SWMM output
ts = results.link_time_series(results.link_ids[0], variable="flow_rate")
print(f"Peak flow: {ts.peak:.4f} at {ts.peak_time}")
# Compare peak flows across return periods
all_results = load_results(r"C:\path\to\project.iddx")
for rp, r in sorted(all_results["Proposed"].items()):
ls = r.link_summary(results.link_ids[0])
print(f"{rp:.0f}-yr: {ls.peak_flow:.4f}")InfoDrainage-Python-Package/
├── pyproject.toml <- Package metadata, dependencies, CLI entry point
├── README.md <- This file
├── iddx_core/ <- The Python package
│ ├── __init__.py <- Public API exports (v0.4.2)
│ ├── model.py <- IddxModel (open/save/create)
│ ├── phase.py <- Phase (scenario container)
│ ├── nodes.py <- Catchment, Junction, DrainageSystem, HEC-22 inlet classes
│ ├── connections.py <- Connection (pipes, channels, bypass), CrossSection, RatRes
│ ├── rainfall.py <- RainfallSource, StormEvent
│ ├── results.py <- ResultsReader (read SWMM .out binary files)
│ ├── enums.py <- RunoffMethod, OutletType, Hec22InletType, etc.
│ ├── utils.py <- XML helpers, GUID generation
│ ├── exceptions.py <- Typed exceptions (IddxParseError, ResultsError, etc.)
│ └── cli.py <- Command-line interface (iddx command)
└── examples/ <- Ready-to-run example scripts
- Fixed: Bypass connections (
CustomCon) were silently dropped during model parsing — addedCUSTOM_BYPASStoConnectionTypeandCustomContoALL_CONNECTION_TAGS - Added full HEC-22 inlet configuration parsing:
Hec22InletConfig,GutterDetail,GrateInletParams,CurbInletParams,ComboInletParams,SlottedInletParams InletDetailnow reads and writes the completeHEC22InCapDetXML block (gutter geometry, grate/curb/combo/slotted sizing params, clogging, depression, location)- Added
CrossSectionDetails— parses custom cross-section geometry on bypass connections (CrsSctDetails) - Added
RationalResults— parses rational-method design output (RatRes) on connections - Added
UpstreamTotals— parses accumulated upstream totals (USTot) on connections - Added
Connection.is_bypassproperty andConnection.conduit_height_userfield - New enums:
Hec22InletType,InletCapacityType,InletLocation - Round-trip verified: 32 bypass connections and 43 HEC-22 inlets preserved through save/reload
- Fixed: Inlet edits now persist on save —
Junction.to_xml()andDrainageSystem.to_xml()re-serialize inlets from Python objects instead of keeping stale raw XML - Fixed:
InletDetailnow reads and writes all XML fields:Type,ICapType,Dest,BCGUID(bypass destination) - Added
InletDetail.from_xml()/.to_xml()methods for proper round-trip serialization - Added
Hec22Resultsdataclass — reads HEC-22 inlet capacity results (approach flow, bypass flow, captured flow, spread, depth) from the XML - Added
InletDetail.capacity_type,.inlet_type,.bypass_dest_guid,.bypass_dest_labelproperties - Exported
InletDetail,InletSource, andHec22Resultsfromiddx_core
- Added
pyproject.toml— installable viapip install .with optional dependency groups - Added
iddxCLI withsummary,pipes,compare, andvalidatecommands - Added typed exceptions:
IddxError,IddxParseError,IddxValidationError,ResultsError,ElementNotFoundError - Added structured logging via Python
loggingmodule - Design validation checks model data and cross-references SWMM simulation results
- SWMM results checks: flooding, surcharging, velocity (self-cleansing and erosion limits)
- Initial public release
- Read/write
.iddxproject files - Parse SWMM
.outbinary result files - Phase cloning, scenario comparison, CSV export