[add_bundle_manual][doroutes.add_bundle_manual] is the bundle (multi-wire) counterpart of
[add_route_manual][doroutes.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
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.
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)
Same Route via Relative Steps
[add_bundle_manual][doroutes.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][doroutes.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.