route_batch(enforce_endpoint_dirs=True) makes cardinal endpoint directions hard constraints: each route leaves its start along start_dir and arrives at its stop travelling stop_dir, the inverse of the stop port's outward normal. A route whose first or last step is blocked is not routed. Off by default, where the directions only label the result, so existing callers route as before.
1.5.0 — 2026-09-28
Added
Edge-midpoint port access for multilayer routing (RoutingConfig.port_access="edge", the new default): a route terminates just outside the port metal on the port normal, enters through the port's edge midpoint perpendicular to that edge, and abuts the port polygon flush (port_entry_depth_um, default 0). Every port polygon — pads included — is now a hard obstacle for every other net, so no net routes through another net's pad. port_access="center" restores the previous polygon-centre terminals.
A minimum straight run out of every port (RoutingConfig.port_access_straight_um, default three router grid units): the route leaves or enters a port along the port normal for that distance before it may turn or via, and the part of the run beyond clearance + wire/2 is reserved as an obstacle on every routing layer so no other net can sit on the port axis in front of the port.
Route+draw passes in the batch router (BatchRoutingParams.reroute_passes, default 6, stopping early when a pass makes no progress): nets whose planned geometry cannot be drawn are re-routed with all metal drawn so far as hard obstacles; a net the router cannot reach at all has the nets walling in its terminal ripped up (at most twice each) and re-routed with it; nets that failed in an earlier pass are drawn first in the next one.
Path passes for edge access: layer transitions the router reports at two XYs are rebuilt on the run's axis; a terminal run that sits up to two grid cells beside the port axis is slid onto it (or, when its far-end via cannot move, the jog is placed at the first clear row from the far end); every candidate is validated at draw time and the router's own path, with only metal-preserving cleanups, is the last fallback.
make_pin(component, name=..., port=...) designates a top-level pin: the port is promoted to the parent and the physical polygon it sits on becomes a virtual obstacle on every configured routing metal for all other nets, without drawing any metal. Search, draw-time validation and port fills honour it.
Changed
The corner-path cleanup pipeline both multilayer engines run (via-pair normalisation, jog coalescing, detour and staircase collapses, endpoint anchoring) lives in doroutes.multilayer.cleanup.PathCleanup; the serial engine no longer carries its own copy.
Route cells place their a/b endpoint ports exactly on the device ports they connect to (opposite orientation, same layer and width), so get_netlist() and elvis see the connection by abutment; endpoints are marked on the (layer, 16) pin datatype and no longer drawn as kfactory port triangles on the metal layer.
The batch router is told the drawn wire width: obstacle rasterisation uses the wire half-width in fixed-width mode too, and layer spacing/width handed to the DRT include the wire width, so two wires wider than the grid no longer land on adjacent cells.
Draw-time obstacle validation decomposes merged L/T-shaped obstacles into rectangles instead of using one bounding box per shape, which no longer fences off the empty corners next to a pad and its feed wire; it also sees other nets' reserved approaches, so a cleaned-up path cannot be drawn where the router was not allowed to go.
The router grid is aligned with the terminals (most common residue, stop terminals first), so a route leaves a port on the port axis instead of one half-cell beside it.
_coalesce_via_jogs absorbs jogs up to one grid unit (was: strictly shorter), moves a via off a terminal instead of skipping it, and never drags a via onto a terminal; _collapse_same_layer_staircases leaves a staircase that ends on the stop axis alone under edge access; a diagonal drawn next to a port bends away from the port.
A* routing (find_route_astar, add_route_astar, add_bundle_astar) no longer re-verifies the API key over the network on every call. A successful check is reused for 10 minutes per process, so repeated routes no longer wait about a second each, and the "API key … found." message is printed once instead of on every route. A missing or rejected key is still reported immediately
Fixed
Multilayer routes to pads no longer produce non-Manhattan metal wedges, sub-min-width slivers, or spacing violations at the pad boundary, and no longer short neighbouring pads on the pad layer.
Path-length matching no longer draws loops outside their segment. loop_side="center" is capacity-checked against its real footprint of (4·loops + 6)·br (it could extend past a short segment's ports), and loop_position="end" with element=0 no longer overruns the corner. Every spliced loop is checked to lie inside its segment before any route is re-placed
Path-length matching refuses routes that contain cells other than the given straight/bend (for example a bend of another radius or a straight of another width), which re-placement used to swap silently. Base bend counts are taken from the placed cells, not from n_bend90 metadata
element now stays on the intended segment when a +2-bend corner overshoot is inserted before it; such requests used to be refused with a misleading "segment too short" error
loop_longest=False with an absolute target below an existing route's length now raises before any route is modified, instead of after re-placing the others
The +2-bend overshoot error states the outgoing-segment length it needs, instead of suggesting ng= or loops
Reusing a PathLengthMatch instance no longer carries the previous call's target or tolerance into the next call
1.3.0 — 2026-08-10
Added
Path-length matching for Route objects through match_path_lengths and the PathLengthMatch constraint model, including exact placed-length matching, group-index weighting, loop and grouped methods, absolute targets, bend-count equalisation, and in-place route updates
A Path-Length Matching tutorial notebook and published documentation page
Static, dynamic, and fullscreen modes for the notebook GDS viewer
Changed
Upgraded the kfactory requirement to 3.0 or newer and adopted its native path-length target API
Migrated the documentation site to Zensical, reorganised reference navigation, and retained GitHub Pages as the deployment target
Updated gdsfactory-facing notebooks to use move and rotate while retaining dmove and drotate in library code for kfactory Instance compatibility
Updated release and documentation workflow dependencies
Fixed
Corrected notebook discovery so the papermill test suite executes notebooks from the repository
Converted Python snippets in the S-Bend MMI tutorial from Markdown to executable code cells
1.2.0 — 2026-07-08
Added
TraceRoute.port1_index / port2_index map each trace back to the caller's ports1 / ports2 lists; traces are ordered by lateral bundle slot, not input order, so use these indices to correlate results
doroutes.util.segments_length_um / doroutes.util.cell_length_um — route lengths are the sum of placed cells' info["length"]; cells lacking it raise ValueError
Composite bundle pcells (pcells.straights / pcells.bends) record info["wire_lengths"] (per-wire sums of their inner cells' info["length"]) and info["length"] (exact total over all wires)
Route objects: routing functions return a doroutes.Route (pydantic model, field-compatible with kfactory's ManhattanRoute for future backend integration, generalized for sbends and coordinate endpoints). It carries the wire's placed instances port-to-port (start→end), start_port/end_port, dbu backbone, n_bend90/bend90_radius/length_straights, and length (µm) via a pluggable length_function defaulting to the exact cell-info sum. corners, path_length, iteration and len() remain as backward-compatible properties; TraceRoute is a deprecated alias
FanLeg.instances / FanLeg.n_bend90 — each fan leg carries its placed cells in start→end order
Changed
Breaking:add_fan_in now returns (stops, legs) instead of just the stops array — legs is the per-wire FanLeg detail (polyline, exact length, original input index, placed instances). Existing callers should unpack: stops, _ = add_fan_in(...)
GDS hierarchy is unchanged from 1.1.0: bundle bodies stay composite straights_*/bends_* segment cells and fan legs stay flat. Per-wire identity lives in the returned Route objects (instances lists the leg cells plus the shared composite segments); bundle traces use a sliced length_function that reads the wire's own wire_lengths entry per composite segment — still exact sums of cells, no geometry
Old-style TraceRoute(corners=..., path_length=...) construction now raises a pydantic.ValidationError (the fields became read-only properties of Route)
add_route_astar and add_route_manual now return TraceRoute instead of a bare list of corner points; iteration (for pt in result) and len() keep working, but isinstance(result, list) checks and list concatenation do not — use result.corners for those
TraceRoute.path_length, bundle per-trace lengths, and FanLeg.length_um are now the exact sum of the placed cells' info["length"] (correct for euler bends; previously approximated geometrically from the corner polyline)
Bundle traces (BundleResult.traces) now span port-to-port including the fan-in/fan-out legs, with exact lengths for the manhattan, sbend, and lbend fan strategies
1.1.0 — 2026-06-04
Added
Public add_route_from_corners_dbu API for routing in database units
Backwards-compatible support for passing lengths in both microns and dbu
1.0.0 — 2026-05-26
Changed
Breaking: standardized public API — routing and bundle functions use micron parameters, always return corners, and follow consistent naming
FanStrategy TypedDict replaces loose keyword arguments for fan-in/fan-out configuration
Deprecation wrappers added for all renamed/removed parameters — old call sites emit warnings instead of breaking
0.6.1 — 2026-05-21
Fixed
Use round() instead of int() for um-to-dbu corner conversion to avoid float truncation
0.6.0 — 2026-05-08
Added
return_corners kwarg on routing and bundle functions
A* clearance parameter for obstacle margin control