From klayoutclaw
Place bonding pads and route nanodevice contacts to pads with multi-window EBL support, different line widths per window, and boundary connection patches between EBL write fields.
How this skill is triggered — by the user, by Claude, or both
Slash command
/klayoutclaw:nanodevice_routingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Route nanodevice contacts to bonding pads, with multi-window EBL write field support (different line widths per window, boundary connection patches).
Route nanodevice contacts to bonding pads, with multi-window EBL write field support (different line widths per window, boundary connection patches).
numpy, scipy, scikit-image (in conda env instrMCPdev)python scripts/place_pads.py --field 2000 --pad-size 80 --pads-per-edge 12 [--layer 2/0] [--margin 60]
--field — EBL write field size in um (default: 2000)--pad-size — Bonding pad side length in um (default: 80)--pads-per-edge — Number of pads per edge (default: 12)--layer — Output layer as layer/datatype (default: 2/0)--margin — Pad center inset from field edge in um (default: 60)Example — 48 pads (12 per edge) around a 2mm field:
python scripts/place_pads.py --field 2000 --pad-size 80 --pads-per-edge 12
Routes device contacts to bonding pads in two passes with different line widths, placing connection patches at the window boundary.
python scripts/route_multiwindow.py \
--pin-contacts 100/0 \
--pin-pads 101/0 \
--inner-window 800 \
--outer-window 2000 \
--inner-width 0.5 \
--outer-width 1.0 \
--inner-layer 3/0 \
--outer-layer 4/0 \
--patch-layer 5/0 \
--patch-size 1.0 \
--obstacle-layers 1/0
--pin-contacts — Layer with pin markers at device contacts (default: 100/0)--pin-pads — Layer with pin markers at bonding pads (default: 101/0)--inner-window — Inner EBL window size in um (default: 800)--outer-window — Outer EBL window size in um (default: 2000)--inner-width — Route line width for inner window in um (default: 0.5)--outer-width — Route line width for outer window in um (default: 1.0)--inner-layer — Output layer for inner routes (default: 3/0)--outer-layer — Output layer for outer routes (default: 4/0)--patch-layer — Output layer for boundary patches (default: 5/0)--patch-size — Boundary patch size in um (default: 1.0)--obstacle-layers — Comma-separated obstacle layers (default: 1/0)The script:
python scripts/clear_routes.py 3/0 4/0 5/0
Clears all shapes from the listed layers. Useful for re-routing without losing device geometry.
place_pads.py (also places pin markers on 101/0)route_multiwindow.py to connect everythingclear_routes.pyNote: These layers are task-specific examples. Always use the layer assignments from the task instruction, which may differ (benchmarks frequently use 20/0 for mesa, 22/0 for topgate, etc.).
| Layer | Purpose | EBL Pass |
|---|---|---|
| 1/0 | Mesa (graphene etch) | Pass 1 |
| 2/0 | Bonding pads | Pass 3 (coarse) |
| 3/0 | Fine routes (<inner window) | Pass 2 (fine) |
| 4/0 | Coarse routes (inner→outer) | Pass 3 (coarse) |
| 5/0 | Boundary patches | Pass 2 or 3 |
| 100/0 | Pin markers: contacts | (removed after routing) |
| 101/0 | Pin markers: pads | (removed after routing) |
All layer parameters use the "L/D" string format (layer/datatype), not arrays or separate integers. Examples: "1/0", "3/0", "100/0".
This applies to all CLI flags (--layer, --inner-layer, --outer-layer, --patch-layer, --obstacle-layers, --pin-contacts, --pin-pads) and to the auto_route MCP tool parameters (pin_layer_a, pin_layer_b, obstacle_layers, output_layer).
When many contacts fan out from a small cluster (e.g. 8 ohmic contacts on a ~30 um device), the last route in the bundle used to fail "No path found": each contact ends up walled in by its sibling-contact pin markers plus the clearance halos of routes already laid, even though a legal, non-crossing path to the pad still exists. This capped connectivity (HM08 ended at 3/8 contacts routed → score 0.387 despite perfect placement).
auto_route now handles this automatically:
rescued_nets (how many nets
needed the rescue) and a rescue_note. After a rescue, still run
route_inspect / evaluate_design with contact_isolation to confirm no
crossings — the rescue is designed never to add one, but verify.If a net still fails after the rescue (genuine over-saturation — a contact
sitting directly on top of a prior route), drop the assignment with
pin_pairs_override to a topology with more corridor room, widen the field, or
fall back to the manual L-route below for that one lead. Do not reflexively
lower map_resolution: a finer grid does not reliably improve dense-fan-out
connectivity and costs ~4x runtime per halving (≈4 s → 15 s → 66 s at
2.0 → 1.0 → 0.5 um on a 2 mm field). Use auto_map_resolution=true only when
contacts are genuinely small (~3 um) and the default under-resolves them.
pin_pairs_override, widen the write field, or route that lead manually.contact_isolation filters; increase path_safe_distance or
narrow path_width if route_inspect flags genuine mid-body crossings.The auto_route MCP tool runs a subprocess on the host machine (KLayout runs on the host, not inside the container). By default it activates conda env instrMCPdev via ~/miniforge3/etc/profile.d/conda.sh. This fails if the host uses a different conda distribution (e.g., anaconda3).
Workaround: Pass the python_path parameter to bypass conda activation entirely. To discover the correct path, use execute_script (which also runs on the host):
import glob, os
candidates = glob.glob(os.path.expanduser("~/anaconda3/envs/instrMCPdev/bin/python3")) + \
glob.glob(os.path.expanduser("~/miniforge3/envs/instrMCPdev/bin/python3"))
result = candidates[0] if candidates else "instrMCPdev env not found"
Then pass the discovered path as python_path in your auto_route call.
When auto_route fails for individual pin pairs (reports "No path found"), create manual L-shaped routes via execute_script:
top_cell = _layout.top_cell()
li_route = _layout.layer(3, 0) # or your route layer
# Create an L-shaped path from contact to pad
x1, y1 = 766.0, 811.4 # contact center (um)
x2, y2 = 928.0, 1825.0 # pad center (um)
mid_x = x2 # route goes horizontal then vertical
width = 1.0 # path width in um
path = pya.DPath([
pya.DPoint(x1, y1),
pya.DPoint(mid_x, y1), # horizontal segment
pya.DPoint(mid_x, y2), # vertical segment to pad
], width / _layout.dbu)
top_cell.shapes(li_route).insert(path)
Use this for any pairs that auto_route couldn't connect. The current MCP response reports routed_pairs, errors, and route metadata via route_inspect; use those fields plus the dry-run pairs[] order to identify which contacts need manual routing.
npx claudepluginhub caidish/klayoutclaw --plugin klayoutclawOrchestrates end-to-end nanodevice design from user query through flake detection, material analysis, geometry creation, routing, evaluation, and save. Device-agnostic methodology derives physics rules from device type and materials.
Routes 17 KiCad MCP tools for schematic creation, PCB layout, autorouting, DRC, and Gerber export. Enforces serialized PCB ops and library-first lookup.
Drives an open-source EDA flow from RTL to GDS with signoff checks (DRC, LVS, RCX) using OpenROAD, Yosys, KLayout, and OpenRCX for PPA iteration and flow diagnosis.