Added
scripts/edgeports.py — deterministic edge port assignment. The real replacement for the --layout libavoid flag removed in 2.0.0.
The problem
draw.io's floating connections attach every edge to the middle of whichever side faces the peer. Give a node three connections leaving its east side and all three land on the same point — they stack into what looks like one line. Most visible on swimlane / cross-functional flowcharts, where handoff edges between lanes share long orthogonal corridors (#96).
What it does
python3 edgeports.py diagram.drawio # in place
python3 edgeports.py diagram.drawio --dry-run # report only- Resolves every vertex to absolute coordinates, through swimlane/container parents — a step at
x=0inside a lane is not at the west edge of the page. - Picks the side of each node facing the other endpoint.
- Groups ends by
(node, side)and sorts each group by the far endpoint's position — so two edges leaving the same side keep their relative order instead of crossing. - Spreads them over evenly-spaced slots: k ends get
1/(k+1) … k/(k+1).
Ends you pinned by hand are skipped, so hand-tuned geometry survives. Re-running is a no-op.
Scope — read this before expecting too much
It is a port assigner, not a router. It separates lines where they meet a shape. It does not stop an edge crossing an unrelated shape mid-run — add <Array as="points"> waypoints for that, or open the file in draw.io desktop and use its editor-side obstacle-avoiding router.
Note also that validate.py --score cannot measure this change: it only scores edges carrying explicit waypoints, and port-pinned edges have none. It reports 0 before and after.
Verified on a 3-lane swimlane — 8 ends pinned, stacked handoffs separated, idempotent on re-run. Ships a --demo self-check covering parent-offset resolution, no-crossing ordering, and idempotency.