Skip to content

Bundle From Corners

add_bundle_manual is the bundle (multi-wire) counterpart of add_route_manual. 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 gdsfactory.gpdk).

Imports

import gdsfactory as gf
from gdsfactory.gpdk import PDK

import doroutes as dr

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 microns).

The bundle's fan-in anchors at the port centroid by default — for this 5-port frame that's (1, 100) on the input side, so the bundle exits the input fan-in at roughly (13, 100) heading east. Each corner you list must be axis-aligned with the previous (manhattan routing), so corners describe a sequence of 90° turns from the bundle exit to the bundle stop on the other side.

The 4-corner Z-shape below detours the bundle through y=30: south at x=50, east at y=30, north at x=80 back to the output bundle line.

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

# Bundle exits at (~13, 100, east) and stops at (~87, 75, east).
# 4-corner Z: south, east, south, then back up to the output centroid y=75.
result = dr.add_bundle_manual(
    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, 100),  # turn south at (50, 100) µm
        (50, 30),  # turn east at (50, 30)
        (80, 30),  # turn north at (80, 30)
        (80, 75),  # back to output centroid y=75; final east leg is implicit
    ],
    spacing=1.0,
    straight="straight",
    bend={"component": "bend_euler", "settings": {"radius": 5}},
)
dr.util.show_cell(c)

Querying Per-Trace Route Lengths

add_bundle_manual returns a BundleResult — the same structured result type as add_bundle_astar. Each .traces[i].path_length gives the geometric path length of that trace in µm — port to port, including the fan-in/fan-out legs, with bend arcs counted as arcs.

Use trace.port1_index and trace.port2_index to map each trace back to the corresponding entry in ports1 / ports2.

ports1_c = [p for p in c.ports if str(p.name).startswith("in")]
ports2_c = [p for p in c.ports if str(p.name).startswith("out")]
for trace in result.traces:
    p1 = ports1_c[trace.port1_index].name
    p2 = ports2_c[trace.port2_index].name
    print(f"Trace {p1}{p2}: {trace.path_length:.1f} µm")
Trace in3 → out3: 335.5 µm
Trace in1 → out1: 267.5 µm
Trace in0 → out0: 206.3 µm
Trace in2 → out2: 267.5 µm
Trace in4 → out4: 335.5 µm

Same Route via Relative Steps

add_bundle_manual with steps= is the relative-coordinate sibling. Each step describes a one-axis move from the previous corner (in microns). Steps anchor at the bundle exit (the post-fan-in convergence point), so step deltas describe the path starting from where the bundle leaves the fan-in. The four supported step forms are:

Key Meaning
dx move by Δx µm from the previous corner
dy move by Δy µm from the previous corner
x absolute x in µm (or "inst,port" to anchor to a port)
y absolute y in µm (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)

# Same Z-shape detour, expressed as relative moves from the bundle exit
# (which sits at ~(13, 100) east of the input ports).
dr.add_bundle_manual(
    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=[
        {"dy": -70},  # south 70 µm to y=30
        {"dx": 37},  # east 37 µm to x=50
        {"dy": 45},  # north 45 µm back to output centroid y=75
    ],
    spacing=1.0,
    straight="straight",
    bend={"component": "bend_euler", "settings": {"radius": 5}},
)
dr.util.show_cell(c)

Choosing Between corners and steps

Both add_bundle_manual(corners=...) and add_bundle_manual(steps=...) 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 fan_in= kwarg, or compose add_fan_in + add_route_manual 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.