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¶
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.