[add_bundle_from_corners][doroutes.add_bundle_from_corners] is the bundle (multi-wire) counterpart of add_route_from_corners and add_route_from_steps. It performs a fan-in at each end so the N wires converge onto a single bundle line, then routes that bundle through user-specified corners or steps.

Reach for it when:

  • the floorplan dictates an exact route shape (a routing channel, fence regions, hand-drawn paths),
  • the same routing recipe needs to apply to many similarly-shaped layouts (use steps= for placement-independent recipes), or
  • A* routing is overkill for a route you already know the shape of.

This is optical routing: bends are bend_euler at the PDK minimum radius (5 µm for cspdk.si220.cband).

Imports

import doroutes as dr
import gdsfactory as gf
from cspdk.si220.cband import PDK

PDK.activate()

A 5-Port Test Frame

dr.pcells.fanout_frame2(transition="ew") puts 5 east-facing inputs on the left edge and 5 west-facing outputs on the right edge of a 100 × 200 µm frame — the same layout used in the A* bundle routing tutorial, so you can compare the algorithmic and corner-based approaches side by side.

c = gf.Component()
ref = c << dr.pcells.fanout_frame2(transition="ew", add_frame=True)
c.add_ports(ref)
dr.util.show_cell(c)

Bundle Through Explicit Corners

Pass corners=[(x1, y1), (x2, y2), ...] to route the bundle through a sequence of absolute coordinate points (in dbu — typically 1 nm). Two corners give a Z-shaped detour: head east, turn south, head west, turn back to the destination.

Each corner contributes one 90° turn in the bundle's path; consecutive segments must be axis-aligned (manhattan), so adjacent corners share either an x or y coordinate.

c = gf.Component()
ref = c << dr.pcells.fanout_frame2(transition="ew", add_frame=True)
c.add_ports(ref)
inv_dbu = dr.util.get_inv_dbu(c.kcl)

dr.add_bundle_from_corners(
    component=c,
    ports1=[p for p in c.ports if str(p.name).startswith("in")],
    ports2=[p for p in c.ports if str(p.name).startswith("out")],
    corners=[
        (50 * inv_dbu, 30 * inv_dbu),  # turn south at (50, 30) µm
        (
            80 * inv_dbu,
            30 * inv_dbu,
        ),  # turn east  at (80, 30) µm — bundle now heads east
    ],
    spacing=1.0,
    bend={"component": "bend_euler", "settings": {"radius": 5}},
    straight="straight",
)
dr.util.show_cell(c)

Same Route via Relative Steps

The same routing function accepts steps=[...] instead of corners=, where each step describes a one-axis move from the previous corner. The four supported step forms are:

Key Meaning
dx move by Δx from the previous corner
dy move by Δy from the previous corner
x absolute x in dbu (or "inst,port" to anchor to a port)
y absolute y in dbu (or "inst,port" to anchor to a port)

The advantage of steps over corners: the recipe is independent of absolute placement, so the same step list works no matter where the parent component is instantiated.

c = gf.Component()
ref = c << dr.pcells.fanout_frame2(transition="ew", add_frame=True)
c.add_ports(ref)
inv_dbu = dr.util.get_inv_dbu(c.kcl)

# Same Z-shape detour, expressed as relative moves from the input centroid.
dr.add_bundle_from_corners(
    component=c,
    ports1=[p for p in c.ports if str(p.name).startswith("in")],
    ports2=[p for p in c.ports if str(p.name).startswith("out")],
    steps=[
        {
            "dx": 50 * inv_dbu,
            "dy": -70 * inv_dbu,
        },  # diagonal first step sets the fan-in anchor
        {"dx": 30 * inv_dbu},  # head east 30 µm
    ],
    spacing=1.0,
    bend={"component": "bend_euler", "settings": {"radius": 5}},
    straight="straight",
)
dr.util.show_cell(c)

Choosing Between corners and steps

Both modes call the same router under the hood; pick whichever feels natural for what you're trying to express:

  • corners — best when the route is anchored to a fixed floorplan (a routing channel, fence region, or specific coordinate constraint). You know the absolute (x, y) points the bundle has to pass through.
  • steps — best when the route shape is what matters and you want it to survive re-placement (the same recipe applies to many similar layouts).

Internally the bundle routes through a manhattan fan-in by default (add_fan_in(strategy="manhattan")). If you need a smoother fan-in (s-bend or l-bend), use add_bundle_astar with a fanin_strategy= kwarg, or compose add_fan_in + add_route_from_corners directly.

Next steps

  • Don't want to spell the path out yourself? See A* Bundle Routing for the algorithmic alternative.
  • For the fan-in stage in isolation (and how to choose between manhattan, s-bend, and l-bend), see Fan-In.
  • For routing between arrayed instances (pad grids, probe cards), see Array Routing — it uses these same corner / step recipes on a real-world layout.