pandid
Generate publication-quality P&IDs and process flow diagrams from a topological flowsheet, in pure Python with no runtime dependencies.
examples/11_ethanol_pid.py: instrumentation, five control loops, hand-isolated valve stations, line numbers, a zone-ruled A3 frame, a title block and a general-notes box. See the gallery for the rest.
You describe what connects to what. The engine lays out the equipment, routes every stream, and draws industry-standard symbols.
Install
Requires Python 3.10 or later. Fully type-hinted and marked
PEP 561, so mypy and pyright read the
annotations straight out of the wheel.
pip install pandid
pip install 'pandid[pdf]' # optional PDF/PNG export backend (wheels only)
pip install 'pandid[yaml]' # optional YAML spec reader (Flowsheet.from_yaml)
The pdf extra is wheels the whole way down (svglib, ReportLab, pypdfium2,
Pillow), so it needs no system libraries and no compiler on any platform.
.svg needs none of it: the engine has zero runtime dependencies.
From a checkout, pip install -e '.[dev]' adds pytest, ruff and mypy.
Quick start
from pandid import Flowsheet, Feed, Heater, Separator, Product
fs = Flowsheet("Flash Separation")
feed = fs.add(Feed("Crude"))
heater = fs.add(Heater("E-101"))
drum = fs.add(Separator("V-101"))
gas = fs.add(Product("Off-Gas"))
liquid = fs.add(Product("Condensate"))
fs.connect(feed.outlet, heater.inlet)
fs.connect(heater.outlet, drum.feed)
fs.connect(drum.vapor, gas.inlet)
fs.connect(drum.liquid, liquid.inlet)
fs.render("flash.svg") # layout and routing run automatically
No coordinates anywhere. render() infers the format from the extension
(.svg, .pdf/.png with the optional backend, or .drawio to keep editing by
hand). fs.to_svg() returns the SVG string, fs.show() opens it in a browser,
and a flowsheet renders inline in Jupyter.
What it does
- Topology-first API. Declare typed units and connect their named ports. Streams are created for you.
- Automatic layout. Sugiyama-style layering, crossing reduction and a centre-aligned flow spine. Recycles are detected and routed around the sheet, and a port that a symbol authors on several faces is piped from the face its peer is actually on.
- Orthogonal A* routing. Right-angle streams with crossing jump-gaps and parallel-segment separation. Never emits a disconnected stream.
- 157 registered symbols with style variants, so a heat exchanger can be
shell-and-tube, plate, kettle or U-tube. They derive from the Apache-2.0
draw.io P&ID stencils (see
NOTICE). - Pixel-perfect overrides.
pin()equipment to exact coordinates and.via()a stream through explicit waypoints. The engine honours both and auto-routes the rest. - Line numbers. A line is labelled the way the line list has it
(
6"-P-1001-A1A: size, service, sequence, spec, with schedule and insulation available too), notS1. The sequence is filled automatically, the number carries through in-line fittings and breaks at a spec break, and the convention is a format string you can replace. - Instrumentation to ISA-5.1. Balloons anchored to the line or the equipment they read, tags drawn inside, location variants, alarms and interlock squares, typed signal lines, and controller outputs landing on a valve's actuator.
- PFD or P&ID.
diagram="p&id"draws the sheet by the P&ID's own conventions, starting with the one every engineer notices: a process line carries no arrowhead.diagram="pfd"is the default and keeps them. - Engineering sheet framing. A full-width title strip with revision history, titled boxes docked to the corners (equipment list, notes, legend), a sectioned stream-property table, and an optional zone-ruled drawing border.
- Declare it as data. A round-trippable spec format (
dict, JSON or YAML) covering all of the above, so an equipment list and a stream table go straight to a drawing. Validated, not interpreted: a typo names the entry and lists what would have worked. - Hand it to draw.io.
fs.render("sheet.drawio")writes an editable diagrams.net model, not a picture: the equipment symbols are draw.io's own P&ID stencils, so the file references them and what opens is a native shape you can drag. draw.io exports.vsdx, so it is also the way to Visio. - A command line.
pandid draw plant.yaml -o plant.pdffor the drawing,pandid validatefor a check a build script can gate on,pandid symbolsfor what can be drawn. - Validation.
fs.validate()flags overlapping pins and off-sheet coordinates as errors, and routes crossing equipment or taking big detours as warnings.
It does not do mass or energy balances. Stream properties are strings you supply, and nothing is calculated from them. This is a drawing engine.
Standards
pandid draws in the idiom of the process-industry drawing standards. It does
not claim conformance to any of them, and nothing it produces has been certified
against one. In summary:
- Equipment symbols follow the conventions of ISO 10628-2.
- Instrument balloons, signal lines and tag letters follow ANSI/ISA-5.1, which is a documented exception under ISO 15519-1 §7.1 rather than the ISO 15519-2 route.
- Line weights follow ISO 15519-1 §6.2 and ISO 15519-2 Annex A.1, label placement ISO 15519-1 §7.2.5, and off-page connector text ISO 15519-1 §9.
- Sheet sizes are the ISO 216 A series, declared in millimetres so a sheet prints at its physical size. The zone grid is an ASME-idiom drawing-frame reference and is not an ISO 5457 grid.
- The title block carries the data fields ISO 7200 specifies.
- Valve fail position is drawn as letters on the authority of PIP PIC001 clause 4.5.3.2, and a normally closed valve is darkened on the authority of clause 4.2.2.7.
Standards in the API reference gives the clause numbers, the quotations, the divergences and what each claim does not cover.
Documentation
| Where | What |
|---|---|
| Example gallery | every example rendered, with what each one demonstrates |
| API reference | every public class, port and option, verified against the source |
| Contributing | setup, the four gates, and the conventions that are easy to get wrong |
| Changelog | what is in this release |
Equipment
A class is a functional equipment type, defined by its ports. Import it from the package and build it:
from pandid import PlateExchanger, ControlValve, StirredTankReactor
fs.add(PlateExchanger("E-1"))
fs.add(ControlValve("FV-1", fail="closed"))
fs.add(StirredTankReactor("R-1"))
The base classes are Feed, Product, Pump, Compressor, Blower, Valve,
Vessel, Tank, HeatExchanger, Heater, Cooler, Reactor, Separator,
Column, Mixer, Splitter, Tee, Reducer, Fitting, Ejector, Vent,
Funnel, Furnace, Turbine, Filter, Dryer, Conveyor and Instrument,
with 42 equipment classes over them — a GearPump is a Pump.
A variant is a drawing within a class, picked with variant=. 93 of the 157
registered drawings get no class of their own, and this is how you reach them:
from pandid import units
fs.add(units.Valve("HV-301", variant="gate", normal_position="closed"))
fs.add(units.Column("T-1", variant="packed", n_feeds=2))
fs.add(units.Fitting("ST-1", variant="strainer"))
The API reference
lists every class's ports and every registered variant, and
Custom equipment
covers a Unit subclass of your own.
Tee is the fitting that branches a line, drawn as three lines meeting with
nothing at the junction. add_valve_station() builds the whole arrangement a
control valve sits in, twelve units and twelve streams, in one call.
Declaring a flowsheet as data
An equipment list and a stream table are data, and usually already exist in a spreadsheet or a simulator export. Hand the engine a plain mapping instead of retyping it as Python.
from pandid import Flowsheet
fs = Flowsheet.from_dict(spec) # a plain dict, from anywhere
fs = Flowsheet.from_json("bfw.json") # standard library only
fs = Flowsheet.from_yaml("bfw.yaml") # pip install 'pandid[yaml]'
spec = fs.to_dict() # writes the same spec back out
fs.render("bfw.svg", border="zone", show_stream_table=True)
Flowsheet.from_dict(fs.to_dict()) rebuilds an equivalent flowsheet. Only
intent is written, never the engine's results, so the file stays short and
re-lays out cleanly. Every failure raises pandid.SpecError, naming the entry
and what would have worked. The
spec format
documents every section and key.
Command line
Installing the package installs a pandid command, so a spec file becomes a
drawing without opening Python. python -m pandid is the same thing from a
checkout.
pandid draw plant.yaml -o plant.pdf --page-size A3 --border zone --stream-table
pandid validate plant.yaml
pandid symbols --kind valve
The exit codes are meant to be gated on: 0 done, 1 the flowsheet was
rejected, 2 the command line was wrong, 3 an optional extra is not
installed. Nothing prints a traceback at a mistyped file name or a typo in the
spec. See the
command line reference.
Examples
Runnable scripts in examples/,
each usable from the repo root or from examples/ itself, and every one
rendered in the
gallery.
| Script | Demonstrates |
|---|---|
01_ammonia_loop.py |
fully automatic layout, layering, recycle detection |
02_manual_layout.py |
pin() by the corner and by the nozzle, .via() overrides, and the debug=True coordinate overlay |
03_distillation_train.py |
two-column train, recycle, stream table, title block with revision history, equipment list / notes / legend |
04_control_loop.py |
ISA balloons attached to the line and to equipment, alarms, an interlock, a PSV, and both loops closing on a valve actuator |
05_reactor_recycle.py |
automatic recycle and purge split, straightened process spine |
06_column_reflux.py |
fractionation sheet: overhead condenser, reflux drum, kettle reboiler taking bottoms off its own draw |
07_metering_skid.py |
in-line fittings and actuated valves on one spine, PSV to flare, level controller on the valve operator |
08_from_data.py |
the whole flowsheet declared as data and built with Flowsheet.from_dict() |
09_line_numbers.py |
full line numbers carried through in-line fittings and broken at two spec breaks |
10_ethanol_pfd.py |
a whole issue-ready sheet on a real A3 page, with six off-page connectors, equipment list, utilities summary and sectioned stream table |
11_ethanol_pid.py |
a whole issued P&ID on a fixed A3 sheet: line numbers on every line, hand-isolated control valve stations, five loops, and a repeated interlock square |
12_block_flow_diagram.py |
the drawing a level above the PFD: one Block per plant section, connections on all four sides, every box sized to its own name and nozzles |
13_mineral_dewatering.py |
a solids circuit as a PFD: thickener, belt filter, conveyor, rotary dryer, recovery cyclone, scrubber, magnetic separator, and tees that combine rather than split |
14_tank_farm.py |
a bulk liquid storage terminal: floating-roof, fixed-roof and pressure storage, a loading rack, a vapour system with its flame arrestors, and loop numbers allocated rather than typed |
15_condensing_turbine.py |
an instrumented sheet the engine lays out on its own: a turbine, an air-cooled condenser, a steam-jet ejector, two loops and an interlock, with no pin() anywhere |
16_demineralised_water.py |
an ion-exchange train laid out automatically under a title strip and an equipment list, with a packed degasser stripped by blower air |
Contributing
See CONTRIBUTING.md.
The gates are pytest, ruff check ., ruff format --check tests and
mypy pandid.
Licence and attribution
pandid is free for individuals, for research and teaching, and for small
companies, under the PolyForm Small Business License 1.0.0.
You may use it at no cost if your company has fewer than 100 people and
under 1,000,000 USD (2019, inflation adjusted) of revenue in its prior tax
year. Students, academics, hobbyists and small consultancies are covered. A
company above either threshold needs a commercial licence. Contact
alexandersonxii+pandid@gmail.com.
This is a source-available licence, not an OSI-approved open-source one, which matters if your organisation screens dependencies.
Equipment symbols are Apache-2.0 and stay that way. They derive from the
draw.io / diagrams.net P&ID stencils, so pandid/render/_vendored_symbols.py and
scripts/vendor_data/drawio/ carry the original licence rather than the one
above, as does the conveyor symbol in pandid/render/symbols.py, which is adapted
from a stencil rather than generated from one. The stencil artwork carries one
additional field-of-use restriction on top of Apache-2.0, naming Atlassian
products and marketplace distribution; it does not reach a drawing you make with
pandid, and NOTICE reproduces it in full for anyone redistributing the
symbols themselves.
NOTICE
says exactly which files are which. The full texts are in
LICENSE and
LICENSE-APACHE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pandid-0.1.2.tar.gz.
File metadata
- Download URL: pandid-0.1.2.tar.gz
- Upload date:
- Size: 3.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f54aec64894114f2e3d102ef65a2416aec0a2b838b2bac403c6566133a033e65
|
|
| MD5 |
1d8de93512053025e0543d3f23ba4ff8
|
|
| BLAKE2b-256 |
3eb98424efca482e8121caeab1282c79f7db6c3e6fcde8eb78507ddaea5747e0
|
Provenance
The following attestation bundles were made for pandid-0.1.2.tar.gz:
Publisher:
release.yml on Alpha9463/pandid
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pandid-0.1.2.tar.gz -
Subject digest:
f54aec64894114f2e3d102ef65a2416aec0a2b838b2bac403c6566133a033e65 - Sigstore transparency entry: 2341558183
- Sigstore integration time:
-
Permalink:
Alpha9463/pandid@914e7362c18eabbb6dd8c0d33dbc72afe8e3eef0 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Alpha9463
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@914e7362c18eabbb6dd8c0d33dbc72afe8e3eef0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pandid-0.1.2-py3-none-any.whl.
File metadata
- Download URL: pandid-0.1.2-py3-none-any.whl
- Upload date:
- Size: 372.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6afa8507e61a5a811f5e5e2b24e6910096a61d949eb3b32b4c4954be888dd87f
|
|
| MD5 |
3730207e20aa17e5c7e94ddee5487b5c
|
|
| BLAKE2b-256 |
bda37f8ccd602785e5edf5f18ccc641df7844e876dee2bdad4e6d8b069b62843
|
Provenance
The following attestation bundles were made for pandid-0.1.2-py3-none-any.whl:
Publisher:
release.yml on Alpha9463/pandid
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pandid-0.1.2-py3-none-any.whl -
Subject digest:
6afa8507e61a5a811f5e5e2b24e6910096a61d949eb3b32b4c4954be888dd87f - Sigstore transparency entry: 2341558189
- Sigstore integration time:
-
Permalink:
Alpha9463/pandid@914e7362c18eabbb6dd8c0d33dbc72afe8e3eef0 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Alpha9463
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@914e7362c18eabbb6dd8c0d33dbc72afe8e3eef0 -
Trigger Event:
push
-
Statement type: