Overview & conventions
Tool names are stable namespace.verb. Each maps to exactly one domain; that domain token is both a klink.find_tools domain=<token> navigation key and a --profile <token> filter.
Reading conventions
name*in a parameter list means required.- Coordinates:
_um= microns (most natural);_dbu= integer database units.micron = dbu × layout.dbu, wheredbucomes fromlayout.info. - Layers: layer index
int,"L/D"string (e.g."1/0"), or{layer, datatype}object. - The Kind column tells you where a tool runs: rpc executes inside the KLayout plugin; local executes in the MCP process. Other badges: read write verify escape mutates (undoable) long (separate timeout).
shape.insert_boxes / shape.insert_many / instance.insert_many / instance.insert_pcell_many. Singleton inserts are for debugging one object.Discover tools live
The catalogue is queried live, never memorized. It is generated from the live plugin's method registry — there is no hand-maintained list that can drift.
from klink import KLinkClient
with KLinkClient() as c:
print([m["name"] for m in c.methods()["methods"]]) # meta.methods
klink.find_tools # no args → domain index
klink.find_tools domain="routing_backends" # that area's tools + usage
klink.find_tools query="lvs route" # ranked matches across all tools
klink.guide reports what is open, the on-disk intent state (declared nets / LVS reports / spec files), and the literal next call for each available intention.
Profiles
--profile filters along two orthogonal axes — intent and domain. Default is read,write,verify,escape.
| Intent | Exposes |
|---|---|
| read | Read-only: layout.info, cell.list, shape.query, view.*, pcell.*, recorder. |
| write | Editing: shape.insert_*, cell.create, layer.ensure, instance.insert*, edit.undo. |
| verify | Checks: drc.run, lvs.run. |
| escape | Escape hatches: exec.python, exec.reset, events.*. |
all | Everything, no filtering. |
python -m klink.mcp --profile read,write,verify,escape # default
python -m klink.mcp --profile read,device_photonics # mix both axes
python -m klink.mcp --profile routing_backends # one domain only
All local tools are always included under any intent profile; klink.find_tools / klink.status / klink.reconnect are always on. Legacy aliases: basic→read, draw→write, advanced→escape, drc→verify.
1 · Connection, self-check, discovery & view connection_and_view · 30 tools
Start here when unsure. klink.status reports connection, active session, interpreter and optional capabilities; klink.guide reports what is open + on-disk intent state + the literal next call; klink.find_tools navigates the rest; klink.reconnect recovers a dropped link. This domain also carries the MCP-side session registry helpers (klink.session_*, klink.transfer_*) — klink drives many KLayout sessions from one MCP bridge; sessions are equal peers, pass the one you mean explicitly. View tools are read-mostly navigation: a freshly created cell is invisible until view.show_cell; view.new_tab opens a disposable scratch tab; view.show_25d opens the native 2.5D viewer from a display list; view.hier_levels raises displayed hierarchy depth if child instances render as name-label boxes. Screenshots (view.screenshot) are a user-requested artifact only — never an agent verification step; prefer geometry queries. Destructive here: view.close_tab — disposable test tabs only.
| Tool | Kind | Params | Function |
|---|---|---|---|
hello | rpc | client, protocol | Introduce the client, receive server info + capabilities. Recommended first call per connection. |
klink.find_tools | local | domain, query | Discover tools by domain or keyword; no args -> domain index, domain=<token> -> that domain's tools + usage, query=<kw> -> ranked matches. |
klink.guide | local | — | Reports what is open, on-disk intent state, and the literal call for each available intention + a suggested next action. |
klink.reconnect | local | — | Close a stale client and try to reconnect to KLayout. |
klink.session_label | local | aliases, description, label*, session_id* | Attach a human label and aliases. |
klink.session_list | local | include_stale | Enumerate discoverable sessions. |
klink.session_resolve | local | query* | Resolve id / label / alias / active cell / top cell to a session. |
klink.session_set_klive_target | local | session_id* | Choose the klive-compatible 8082 endpoint (where c.show() lands). |
klink.session_status | local | include_stale, session_id | One session record (default: active). |
klink.session_use | local | session_id* | Repoint the bridge's primary RPC target. |
klink.status | local | — | MCP bridge connection status, active session, interpreter, last connection error. First stop when debugging. |
klink.transfer_commit | local | dry_run, package_id* | Commit a prepared package. |
klink.transfer_prepare | local | copy_mode, layer_map, source_session*, target_cell, target_session*, translate_um | Build a flat-selection package and dry-run it on the target. |
meta.debug_signals | rpc | fire | SignalHub diagnostics; optionally fire a synthetic event to test delivery. |
meta.methods | rpc | — | Full RPC method catalogue with descriptions + JSON schemas, ready for LLM function-calling. |
meta.ping | rpc | — | Liveness probe; echoes params + trace id. |
view.activate_tab | rpc | index* | Switch the current tab; all single-layout RPCs then act on it. |
view.close_tab | rpc | view_index | Close a layout view tab by index (active tab if omitted). |
view.hier_levels | rpc | max, min | Read or set the view's displayed hierarchy depth (min/max); raise max if dense instances render as name boxes. |
view.highlight | rpc | boxes_um, circles_um, clear, color, expire_s, halo, line_width, polygons_um | Draw transient highlight markers (boxes/polygons/circles) on the view - overlay only, never touches layout/selection/undo. |
view.highlight_clear | rpc | — | Remove all klink highlight markers immediately. |
view.list_tabs | rpc | — | List layout tabs (index, title, file, active cell, current). |
view.new_tab | rpc | cell_name, dbu | Open a new empty layout tab with a fresh top cell and make it current; returns previous_current_index to restore later. |
view.screenshot | rpc | bbox_dbu, bbox_um, height_px, mode, path, width_px | Render a PNG. base64 data URL or path to disk. User-requested only. |
view.show_25d | rpc | cell, displays*, generator | Open KLayout's native 2.5D extruded viewer from a display list (layer + z range per material); z heights are process facts the caller supplies. |
view.show_cell | rpc | cell*, zoom_fit | Set the displayed top cell (makes a new cell visible), zoom-fits by default. |
view.show_lvsdb | rpc | kind, path* | Load a saved LVS/netlist DB into the Netlist Browser and show it. lvs -> .lvsdb, l2n -> .l2n. |
view.viewport | rpc | — | Report current viewport: visible bbox (um and dbu), pixel size, cellview index. |
view.zoom_box | rpc | bbox_dbu, bbox_um | Zoom to exactly the given bbox (um or dbu). |
view.zoom_fit | rpc | — | Fit the whole layout into the viewport. |
Example.
klink.status
klink.guide
view.list_tabs
# Python client equivalent
from klink import KLinkClient
with KLinkClient() as c:
print(c.hello(client="my-script"))
print(c.layout_info(verbosity="summary"))
print(c.ping(nonce=42))
# session registry + two-phase transfer (prepare + dry-run, then commit)
klink.session_list
klink.session_label session_id="klayout-8766" label="scratch" aliases=["test"]
klink.session_resolve query="scratch"
klink.transfer_prepare source_session="klayout-8765" target_session="klayout-8766" copy_mode="flat_selection"
klink.transfer_commit package_id="pkg_0001"
2 · Multi-session registry & cross-session transfer (plugin side) multi_session_transfer · 7 tools
The plugin-side half of session/transfer: session.label_set and session.mark_klive_target manage the shared registry directly from a KLayout window; transfer.pending_set/status/clear and transfer.paste_pending hold and apply a reviewed flat-selection package in this window; transfer.import_cell_tree_package imports a cell tree from a GDS/OAS package via native Cell.copy_tree. The MCP-side orchestration (klink.session_*, klink.transfer_prepare/commit) lives in connection_and_view — start there for the two-phase, confirmation-safe transfer flow.
| Tool | Kind | Params | Function |
|---|---|---|---|
session.label_set | rpc | aliases, description, label*, session_id* | (plugin) Set label/aliases in the shared registry. |
session.mark_klive_target | rpc | — | (plugin) Mark this window as the klive/8082 target. |
transfer.import_cell_tree_package | rpc | dry_run, path*, source_cell* | Import one cell tree from a GDS/OAS via native Cell.copy_tree; conflicts get $N. |
transfer.paste_pending | rpc | clear_after, dry_run | Paste the pending package (already contains final layers/coords). |
transfer.pending_clear | rpc | — | Clear the pending package (no geometry written). |
transfer.pending_set | rpc | package* | Store a reviewed package in this window. |
transfer.pending_status | rpc | — | Pending package status for this window. |
3 · Geometry & cell-structure authoring geometry_authoring · 50 tools
The core drawing surface. Read first: layout.info, cell.list/cell.tree, layer.list/layer.display_list, shape.query, instance.query, pcell.*, library.list. Author with BATCH RPCs for anything generated — never one RPC per object. layer.ensure before drawing. edit.undo/redo/status wrap edits in transactions. Beyond basic authoring this domain also carries: geometry.boolean/cell_xor/density (diff and coverage checks), cell.fill_region (dummy fill / test-structure tiling), cell.flatten, layer.load_lyp/save_lyp/set_style/set_visible (view styling), library.list/refresh/register_file, pcell.convert_to_static, shape.change_layer/transform, instance.transform. Destructive: layout.clear, cell.delete — disposable/test cells only; do NOT touch the user's working tab/layout UNLESS the user explicitly instructs it.
Read
| Tool | Kind | Params | Function |
|---|---|---|---|
layout.info | rpc | verbosity | Snapshot: views, active cellview, top cell, source file, dbu, top-cell list, layers. Refresh your world view. |
cell.list | rpc | limit, name_prefix, offset, top_only, with_bbox | Flat paginated cell list. |
cell.tree | rpc | max_depth, max_nodes, root | Hierarchical cell tree (bounded). |
layer.list | rpc | — | All layers: layer_index, layer/datatype, name, dbu_um. |
layer.display_list | rpc | — | List the current view's layer DISPLAY entries (visible, colors, dither pattern, name) - the view-side counterpart of layer.list. |
shape.query | rpc | bbox_dbu, cell*, kinds, layers, limit | Read shapes from ONE cell (no recursion) as JSON. Narrow with layers+bbox; paginate (default 500, max 5000). |
instance.query | rpc | bbox_dbu, bbox_um, child, limit, parent* | Direct child instances: name, bbox, transform, array, PCell metadata, per-layer shape counts. |
pcell.libraries | rpc | — | Available PCell libraries (Basic + PDKs). |
pcell.list | rpc | library | PCell names in a library (default Basic). |
pcell.info | rpc | library, pcell* | Parameters of a PCell (name/type/default/description/choices). |
library.list | rpc | — | All libraries registered in this KLayout process (Basic, salt/PDK, runtime-registered). |
Write · cells & layers
| Tool | Kind | Params | Function |
|---|---|---|---|
cell.create | rpc | name | New cell (dup name -> $1...). Not idempotent. |
cell.rename | rpc | allow_suffix, cell*, new_name* | Rename; dup name errors unless allow_suffix=true. |
cell.delete | rpc | cell*, recursive | Delete a cell (recursive drops orphaned children). Destructive. |
cell.flatten | rpc | cell*, dry_run, levels, prune | Flatten a cell's hierarchy into plain shapes; dry_run previews, prune removes orphaned children. |
cell.fill_region | rpc | boxes_um, cell*, circles_um, column_step_um, exclude_layers, exclude_margin_um, fc_bbox_um, fill_cell*, origin_um, polygons_um, region_layers, row_step_um | Tile a fill cell across a region (boxes/polygons/circles/region_layers minus exclude_layers) - KLayout's Fill Utility for dummy fill, device arrays, test structures. |
layer.ensure | rpc | datatype, layer*, name | Upsert a GDS layer; returns layer_index. |
layer.set_style | rpc | color, dither_pattern, fill_color, frame_color, layer*, line_width | Style one layer's display (color/fill/frame/dither/line width) in the current view - display only, layout data untouched. |
layer.set_visible | rpc | exclusive, layers*, visible | Show/hide layers in the current view; exclusive=true shows ONLY the listed layers. |
layer.load_lyp | rpc | path* | Load a KLayout .lyp layer-properties file into the current view (colors/stipples/visibility) in one call. |
layer.save_lyp | rpc | path* | Save the current view's layer properties (colors/stipples/visibility) to a .lyp file. |
library.refresh | rpc | library | Re-evaluate library content in every layout that uses it, e.g. after a PCell was re-registered. |
library.register_file | rpc | description, name, path*, technology | Register a layout file as a runtime library so its cells become placeable by name via instance.insert*. |
Write · shapes (batch-first)
| Tool | Kind | Params | Function |
|---|---|---|---|
shape.insert_boxes | rpc | boxes_dbu, boxes_um, cell*, datatype, dry_run, layer, layer_index | Batch many rectangles on one cell/layer. |
shape.insert_many | rpc | cell*, dry_run, items* | Batch mixed box/polygon/path/text. |
shape.insert_box | rpc | bbox_dbu, bbox_um, cell*, datatype, layer, layer_index | One rectangle. |
shape.insert_polygon | rpc | cell*, datatype, layer, layer_index, points_dbu, points_um | One polygon (hull only), auto-closed. |
shape.insert_path | rpc | begin_ext_dbu, begin_ext_um, cell*, datatype, end_ext_dbu, end_ext_um, layer, layer_index, points_dbu, points_um, round_ends, width_dbu, width_um | One path (center line + width). |
shape.insert_text | rpc | cell*, datatype, layer, layer_index, position_dbu, position_um, size_dbu, size_um, string* | One text label (annotation, no mask geometry). |
shape.delete | rpc | all_layers, bbox_dbu, bbox_um, cell*, datatype, dry_run, kinds, layer, layer_index, layers, limit | Delete shapes by selector; dry_run previews count. One transaction. |
shape.change_layer | rpc | bbox_um, cell*, from_layer*, to_layer* | Move shapes from one layer to another within a cell (optionally only those touching a bbox). |
shape.transform | rpc | bbox_um, cell*, layers, limit, mirror, move_um, rotation | Move/rotate/mirror EXISTING shapes in place matching a filter (layers and/or bbox required). |
Write · instances & PCells (batch-first)
| Tool | Kind | Params | Function |
|---|---|---|---|
instance.insert_many | rpc | dry_run, items*, parent* | Batch child-cell instances. |
instance.insert_pcell_many | rpc | dry_run, items*, parent* | Batch PCell instances. |
instance.insert | rpc | array, child*, klink_id, library, magnification, mirror, parent*, position_dbu, position_um, rotation | Place child in parent; optional array grid. |
instance.insert_pcell | rpc | array, klink_id, library, magnification, mirror, params, parent*, pcell*, position_dbu, position_um, rotation | Build a PCell variant then insert. Call pcell.info first. |
instance.delete | rpc | all, bbox_dbu, bbox_um, child, dry_run, limit, parent* | Delete instances by selector (non-destructive to the child cell). |
instance.transform | rpc | bbox_um, child, mirror, move_um, parent*, rotation | Move/rotate/mirror PLACED instances matching a filter (child and/or bbox); zero matches errors. |
pcell.register_fitted | rpc | fit_table*, name* | Register a fitted-device PCell at runtime into klink_structdevice; no plugin reload. |
pcell.convert_to_static | rpc | cell*, prune_variant | Convert a PCell variant into a static cell and retarget every instance to it; geometry is then frozen. |
Write · layout-level & edit history
| Tool | Kind | Params | Function |
|---|---|---|---|
layout.show_file | rpc | keep_position, mode, path*, technology | Load GDS/OAS (reload if open). replace or new tab. |
layout.save_file | rpc | cellview_index, path* | Save; extension picks GDSII/OASIS. |
layout.import_file | rpc | create_other_layers, layer_map, on_conflict, path* | MERGE a layout file into the ACTIVE layout with layer remap + cell-name-conflict policy (rename/add/overwrite/skip). |
layout.clear | rpc | cellview_index | Destructive: clear the whole layout. |
edit.undo | rpc | — | Undo last undoable op; returns before/after stack. |
edit.redo | rpc | — | Redo. |
edit.status | rpc | debug | Undo/redo availability. |
Geometry checks (read-only reports)
| Tool | Kind | Params | Function |
|---|---|---|---|
geometry.boolean | rpc | a*, b*, op*, write_to | Boolean (and/or/xor/not) between two {cell, layer} sources; reports polygon_count/area, optionally writes the result to write_to. |
geometry.cell_xor | rpc | cell_a*, cell_b*, layers, only_differing | Geometric diff between two cells per layer (pure report, writes nothing) - the tool for 'did my edit change only what I intended'. |
geometry.density | rpc | cell*, layer*, window_um | Covered-area density of one layer in a cell (area / window area); the pre-check for dummy-fill decisions. |
Example.
# 1) read
layout.info verbosity="summary"
cell.list top_only=true
# 2) create cell + ensure layer
cell.create name="MYBLOCK"
layer.ensure layer=1 datatype=0 name="M1"
# 3) batch-draw a row of rectangles (microns) -- never loop single inserts!
shape.insert_boxes cell="MYBLOCK" layer="1/0" boxes_um=[[0,0,10,4],[20,0,30,4],[40,0,50,4]]
# 4) mixed shapes in one call
shape.insert_many cell="MYBLOCK" items=[
{"kind":"box","layer":"1/0","bbox_um":[0,10,50,14]},
{"kind":"path","layer":"2/0","points_um":[[0,20],[50,20]],"width_um":2},
{"kind":"text","layer":"63/0","position_um":[0,26],"text":"MYBLOCK","size_um":4}
]
# 5) make it visible (a new cell is invisible until show_cell)
view.show_cell cell="MYBLOCK"
# PCell placement (check params first)
pcell.info library="Basic" pcell="CIRCLE"
instance.insert_pcell parent="MYBLOCK" library="Basic" pcell="CIRCLE" \
params={"l":"1/0","r":5.0,"n":64} position_um=[100,0]
# one call places an 8x8 grid
instance.insert parent="TOP" child="MYBLOCK" position_um=[0,0] \
array={"rows":8,"cols":8,"pitch_x_um":60,"pitch_y_um":40}
4 · Selection & SEND interaction memory selection_and_send_memory · 10 tools
Two distinct things. selection.* is the LIVE current selection in KLayout: selection.get, selection.set_box (replaces current selection), selection.clear, selection.send_context (agent-side explicit SEND). interaction.* is durable session memory of selections the user explicitly SENT (toolbar SEND, recorded as ids like sel_0006): interaction.selection.latest/recent (default latest 5, ordered NOT time-pruned)/get/label, and interaction.context (current selection + recent memory together). Use these whenever the user says "just sent", "this area", "here", "that one". Resolve by order/count, not age. Bind user phrases to these ids/queries, NOT to screenshots.
| Tool | Kind | Params | Function |
|---|---|---|---|
interaction.context | local | include_current_selection | Current selection + recent SEND memory together. |
interaction.selection.clear_session | local | confirm* | Clear this session's interaction context after confirmation. |
interaction.selection.get | local | id* | One stored selection by id. |
interaction.selection.label | local | description, id*, label | Attach a label/description. |
interaction.selection.latest | local | — | Latest stored SEND. |
interaction.selection.recent | local | limit | Recent SENDs (default latest 5, by order). |
selection.clear | rpc | — | Clear the current selection. |
selection.get | rpc | limit | Current selection (shape or instance). Empty = empty list, not an error. |
selection.send_context | rpc | max_items, source | Emit the current selection as a selection_sent event (agent-side SEND). |
selection.set_box | rpc | bbox_dbu, bbox_um, cell*, include_instances, layers, limit | Select all shapes intersecting a box on given layers; replaces current selection. |
Example.
# user selects A -> SEND, selects B -> SEND
interaction.selection.recent limit=2 # -> [sel_0007(B), sel_0006(A)]
interaction.selection.label id="sel_0006" label="probe pad"
# now act by id, not by guessing position from a screenshot
5 · Ports & anchors (routing markers) ports_and_anchors · 18 tools
Ports are net endpoints (klink_Port PCells: net + orientation + width). Anchors are routing constraints (klink_Anchor PCells) whose kind is waypoint_region, bend_region, or corridor (a plain corridor is a REQUIRED pass-through; label it choice_group=BUS for an OPTIONAL channel). port.mark/list/update/transform/set_layer/unmark/delete_all/repair_names; the same verbs on anchor.* (+ anchor.repair_ids). port.mark_many marks many ports in one cell with a single RPC/undo step (validate-before-mutate) — use it instead of looping port.mark for generated port arrays. port.harvest_blackbox derives Ports from LIVE gdsfactory/PDK blackbox instance positions via the waveguide stub convention — a photonics-adjacent tool that lives in this domain. Routing tools default port_layer=999/99, anchor_layer=999/1. Keepouts are NOT an anchor kind — pass your OWN design's obstacle layer(s) to routing tools as obstacle_layers; klink ships no default keepout layer (900/0 is klink's reserved keepout layer, used internally by structdevice). These are the INPUT to routing_backends: mark Ports+Anchors, then call a routing.* tool.
| Tool | Kind | Params | Function |
|---|---|---|---|
port.set_layer | rpc | layer* | Configure the default Port marker layer. |
port.mark | rpc | access_mode, cell*, center_dbu, center_um, label, layer, name, net, orientation, port_type, show_label, slide_allowed, slide_edge, target_layer, width_um | Create one klink_Port PCell (a net endpoint). |
port.mark_many | rpc | access_mode, cell*, items*, label, layer, net, orientation, port_type, show_label, slide_allowed, slide_edge, target_layer, width_um | Create many Port PCells in one cell in one call/undo step; validated before any insert (one bad item rejects the whole batch). |
port.list | rpc | cell*, layer, sort | List Ports in a cell. |
port.update | rpc | access_mode, cell*, label, layer, name*, net, orientation, port_type, show_label, slide_allowed, slide_edge, target_layer, width_um | Update one Port by immutable name. |
port.transform | rpc | access_mode, cell*, label, layer, names, net, orientation, port_type, rotate_delta, selection, show_label, slide_allowed, slide_edge, target_layer, width_um | Batch-update Ports by names or GUI selection. |
port.repair_names | rpc | cell*, layer, prefix | Repair duplicate/empty Port names (GUI-inserted). |
port.harvest_blackbox | local | cell*, clear, nets, port_layer, stub_size_um*, tags*, wg_layer* | Derive Ports from PDK blackbox instances via the waveguide stub convention. Re-run after moving instances. |
port.unmark | rpc | cell*, name* | Delete one Port. |
port.delete_all | rpc | cell*, layer | Delete all Ports in a cell. |
anchor.set_layer | rpc | layer* | Configure the default Anchor marker layer. |
anchor.mark | rpc | cell*, center_dbu, center_um, height_um, id, kind, label, layer, mode, name, net, orientation, path_points, priority, radius_um, required, show_label, width_um | Create one klink_Anchor PCell (routing constraint). |
anchor.list | rpc | cell*, layer, sort | List Anchors in a cell. |
anchor.update | rpc | cell*, height_um, id*, kind, label, layer, mode, net, new_id, orientation, path_points, priority, radius_um, required, show_label, width_um | Update one Anchor by immutable id. |
anchor.transform | rpc | cell*, height_um, ids, kind, label, layer, mode, names, net, orientation, path_points, priority, radius_um, required, selection, show_label, width_um | Batch-update Anchors. |
anchor.repair_ids | rpc | cell*, layer, prefix | Repair duplicate/empty Anchor ids. |
anchor.unmark | rpc | cell*, id* | Delete one Anchor. |
anchor.delete_all | rpc | cell*, layer | Delete all Anchors in a cell. |
Example.
port.mark cell="NET1" name="A" center_um=[0,0] orientation="E" width_um=2 net="sig"
port.mark cell="NET1" name="B" center_um=[80,20] orientation="W" width_um=2 net="sig"
anchor.mark cell="NET1" kind="waypoint_region" center_um=[40,40] radius_um=6 net="sig"
port.list cell="NET1"
routing.tapered_hybrid_cell cell="NET1" angle_mode="manhattan" obstacle_layers=["10/0"]
6 · Routing backends routing_backends · 9 tools
All read Port/Anchor PCells in a cell and write routes. Pick by topology/quality: routing.tapered_hybrid_cell is the main path+patch backend; routing.tapered_polygon_cell writes continuous taper polygons (first-class, not a fallback); routing.steiner_cell handles multi-terminal nets (>2 ports); the routing.damped_* family adds explicit extra obstacle clearance; routing.global_channel_cell is a global-decision router (candidate-sink assignment + corridor-capacity load-balancing) on top of tapered hybrid geometry; routing.multilayer_escape_cell routes wall-blocked nets via a bridge layer + vias; routing.gdsfactory_ports routes Port markers with a named gdsfactory strategy (needs gdsfactory in the interpreter). Always inspect the structured result: ok=false, obstacle_hit_count>0, sibling overlaps, short route_count all mean failure. Pass your own design's obstacle_layers (no default).
| Tool | Kind | Params | Function |
|---|---|---|---|
routing.damped_polygon_cell | local | anchor_layer, angle_mode, cell*, clear, corner_style, damping_distance_um, obstacle_layers, port_layer, route_layer, spacing_um | Damped continuous taper polygons. |
routing.damped_segment_cell | local | anchor_layer, angle_mode, cell*, clear, damping_distance_um, obstacle_layers, port_layer, spacing_um | Extra obstacle clearance, segment output. |
routing.damped_steiner_cell | local | anchor_layer, angle_mode, cell*, clear, damping_distance_um, obstacle_layers, port_layer, root_ports, route_layer, spacing_um | Damped multi-terminal trunk/branch. |
routing.gdsfactory_ports | local | all_two_port_nets, allow_crossing, auto_taper, backbone_um, bundle_gather_um, cell*, clear, collision_check_layers, cross_section, distance_um, end_straight_um, gf_route_layer, min_straight_taper_um, net, obstacle_bboxes_um, output_mode, pair_by, path_length_match, port_layer, radius_um, resolution_um, route_layer*, route_width_um, router, sbend_fallback, separation_um, sort_ports, source, source_orientation, source_prefix, start_straight_um, steps, taper, target, target_orientation, target_prefix, waypoints_um | Route Port markers with one named gdsfactory strategy. Needs gdsfactory in the interpreter. |
routing.global_channel_cell | local | anchor_layer, angle_mode, cell*, clear, obstacle_layers, port_layer, safe_distance_um, spacing_um | Stronger global-decision router: candidate assignment + corridor-capacity load-balancing, reuses hybrid geometry. |
routing.multilayer_escape_cell | local | bridge_layer*, cell*, clear, obstacle_layers, port_layer, route_layer*, spacing_um, via_layer* | Wall-blocked nets via bridge layer + vias. |
routing.steiner_cell | local | anchor_layer, cell*, clear, obstacle_layers, port_layer, root_ports, route_layer | Multi-terminal nets (>2 ports). |
routing.tapered_hybrid_cell | local | anchor_layer, angle_mode, cell*, clear, obstacle_layers, port_layer, spacing_um | Main path+patch backend. angle_mode: any/manhattan/fortyfive. |
routing.tapered_polygon_cell | local | anchor_layer, angle_mode, cell*, clear, corner_style, obstacle_layers, port_layer, route_layer, spacing_um | Continuous taper polygons (first-class). |
routing.gdsfactory_ports routers (a parameter the chosen router can't honor is an error naming the routers that honor it, never silently ignored):
| router | Use |
|---|---|
bundle (default) | Manhattan river routing with separation; also honors waypoints/steps, radius_um, start/end_straight_um, path_length_match, collision_check_layers. |
electrical | bundle + metal defaults + sharp corners + electrical port typing. |
sbend | Smooth S-transition for laterally offset, facing ports. |
all_angle | Non-Manhattan bundle (optional backbone_um spine). |
single | Independent Manhattan route per pair. |
dubins | Arc-based any-heading route per pair. |
astar (experimental) | Grid A* around obstacle_bboxes_um; gf's astar is fragile, so klink verifies the result and errors instead of returning a wall-crossing route. For reliable avoidance use klink's own tapered_hybrid/damped + obstacle_layers. |
Example.
routing.tapered_hybrid_cell cell="BLOCK" angle_mode="manhattan" spacing_um=20 obstacle_layers=["900/0"]
routing.damped_segment_cell cell="BLOCK" damping_distance_um=15 obstacle_layers=["900/0"]
routing.steiner_cell cell="BLOCK" route_layer="1/0"
routing.gdsfactory_ports cell="BLOCK" route_layer="1/0" router="bundle" \
separation_um=5 radius_um=10 path_length_match=true
match=True — marker counts and "looks routed" never substitute. Check ok, obstacle_hit_count, sibling overlap and route_count in the structured result.7 · DRC & LVS verification drc_and_lvs_verification · 2 tools
Both are long-running, pure pya, domain-agnostic. drc.run runs arbitrary DRC DSL (Ruby) you supply against the layout — exceptions inside the script come back as results, they do NOT fail the RPC. lvs.run is the connectivity counterpart: extracts the live layout into a device netlist and compares it against a REFERENCE netlist you supply, writes a native .lvsdb and (default) opens it in the Netlist/LVS browser. A P&R/device stage counts as DONE only on a real live LVS match=True — offline fixtures and marker counts never substitute. For the structdevice flow prefer structdevice.lvs_check.
| Tool | Kind | Params | Function |
|---|---|---|---|
drc.run | rpc | code*, input_layout, output_rdb, result_mode, stderr_limit, stdout_limit, top_cell | Run arbitrary DRC DSL in KLayout's Ruby DRC engine. With source() = standalone, else interactive on the current layout. |
lvs.run | rpc | cell*, conductors*, devices*, out_lvsdb, reference*, show, vias | Extract (per-cell extractors + conductor layers) -> compare to reference (reference.spice or reference.netlist) -> write .lvsdb and show. |
Example.
# DRC: M1 minimum spacing 0.2um (interactive, against the current layout)
drc.run script="""
m1 = input(1, 0)
m1.space(0.2.um).output("M1_space", "M1 spacing < 0.2um")
"""
# LVS: extract conductor layers, compare to a reference SPICE, open the browser
lvs.run cell="BLOCK" conductors=["1/0","3/0"] vias=["2/0"] \
devices={...} reference={"spice":"ref.spice"} out_lvsdb="block.lvsdb" show=true
8 · Custom-device netlist → auto P&R → LVS device_structdevice · 6 tools
This is the device-AGNOSTIC custom-device P&R flow. A "device" is any cell with an arbitrary parameter set + terminals; klink assumes no parameter names/count and no device vocabulary. The device library, process profile, and terminal source are EXAMPLE/PDK data passed in explicitly — the tools ship none and return an instructive "write/run an example" error. structdevice.build_from_netlist is the headline one-call flow (confirmation-gated: call once for a proposal, again with the confirm token to build); routing runs on the flexdr engine using a compact physical model where the device's own metal layers double as routing layers. structdevice.declare_nets / connect_nets / lvs_check / spec_write are the SEND-driven interactive path. structdevice.register_pcell wraps the lower-level plugin RPC pcell.register_fitted (listed under geometry_authoring).
| Tool | Kind | Params | Function |
|---|---|---|---|
structdevice.build_from_netlist | local | cell*, cols, confirm, mode, netlist*, rows, session | Headline one-call flow: device-level netlist -> floorplan -> single-pass multilayer routes -> draw -> device-LVS a fresh cell. Confirmation-gated. |
structdevice.connect_nets | local | cell*, conductors, min_spacing_um, min_width_um, route_layer, route_width_um, session, via_cell, vias | Wire declared-but-unconnected nets + verify; any LVS mismatch undoes everything. |
structdevice.declare_nets | local | cell*, conductors, recent_sends*, vias | One SEND framing >=2 terminals = one declared net (persisted). Example-driven. |
structdevice.lvs_check | local | cell*, conductors, mode, session, vias | Net-level reconcile; device/both also run device-level NetlistComparer. |
structdevice.register_pcell | local | diff_report, fit_table*, name*, session | Register a fitted-device PCell at runtime; zero plugin reloads. |
structdevice.spec_write | local | cell*, conductors, device_class, layer_roles*, session, vias | Project a live cell into a klink.spec.json fact file. |
Example.
# step 1: no confirm -> proposal (grid rows x cols, row pitch, routing layers, device mix)
structdevice.build_from_netlist cell="RINGOSC" netlist={
"instances":[{"name":"INV0","device":"inv_x1"}, ...],
"nets":[{"name":"a","terminals":["INV0/in","INV2/out"]}, ...],
"groups":[]
} mode="3L"
# -> needs_confirmation + proposal + next_action(confirm=...)
# step 2: same args + confirm token -> actually place/route/draw/LVS
structdevice.build_from_netlist cell="RINGOSC" netlist={...} mode="3L" confirm="CONFIRM-xyz"
# SEND-driven interactive path
structdevice.declare_nets recent_sends=3 cell="BLOCK" conductors=["1/0","3/0"] vias=["2/0"]
structdevice.connect_nets cell="BLOCK" route_layer="3/0" route_width_um=0.5
structdevice.lvs_check cell="BLOCK" mode="both"
structdevice.spec_write cell="BLOCK" layer_roles={"1/0":"gate","3/0":"metal1"}
9 · Imaging (cross-section / 3D / SEM / Blender) imaging · 4 tools
Everything runs klink-side (no plugin involvement); heavy deps are optional and the error names the exact pip install command. Recipes/VisualStack instances are example-owned — klink ships mechanism only. imaging.xsection_run makes a process cross-section along an explicit cut line from a .pyxs recipe (engine klayout_pyxs); steps=true + '# klink-step: <name>' recipe markers give a per-step film. imaging.render3d builds a GLB plus a self-contained interactive viewer HTML; mode=fast extrudes a visual-stack declaration, mode=process sweeps the xsection engine so process curvature (LOCOS, CMP) is real; fraction<1 exposes a true cutaway section. imaging.sem_top renders deterministic SEM-style top-view PNGs (greyscale + false color). imaging.blender renders a paper-grade image via a headless bpy subprocess — mode=die polishes a render3d GLB, mode=figure builds a device figure from GDS+stack at 1:1 layout coordinates (lattice layers become atomic-structure motifs).
| Tool | Kind | Params | Function |
|---|---|---|---|
imaging.blender | local | basename, camera, cell, gds, glb, lattice_a_um, mode*, output_dir*, overwrite, samples, session, slabs, stack, timeout_s, transparent | Paper-grade Blender render via headless bpy subprocess. mode=die polishes a render3d GLB; mode=figure builds a device figure from GDS+stack at 1:1 layout scale (lattice layers -> atomic motifs). |
imaging.render3d | local | basename, cell, exclude, fraction, gds, mode, output_dir*, overwrite, recipe, session, slices, stack* | Build a 3D model (GLB) plus a self-contained offline interactive viewer HTML. mode=fast extrudes a visual-stack declaration; mode=process sweeps the xsection engine so process curvature (LOCOS/CMP) is real. |
imaging.sem_top | local | basename, cell, corner_radius_um, gds, layers, output_dir*, overwrite, seed, session, stack*, width_px | SEM-style top-view PNGs (greyscale + false color) from a visual-stack declaration, with grain/scanlines/vignette; deterministic (seeded). |
imaging.xsection_run | local | basename, below_um, cell, cut_um*, delta_dbu, depth_um, exclude, extend_um, gds, height_um, output_dir*, overwrite, recipe*, render, session, show, stack, steps | Process cross-section along an explicit cut line from a .pyxs recipe (engine klayout_pyxs); steps=true writes one section per marked process step. |
10 · Nanodevices (Hall bar / EBL / flake) device_nanodevice · 2 tools
nanodevice.hallbar is a one-call closed loop: from a HallBarSpec (bar length/width, contact_count, contact/pad dims, pitch, gaps) it computes and draws the whole device (bar + N symmetric contact arms + pads + Port markers + labels), then delegates routing to the generic router (overlap validation on, optional EBL writefield walls as keepouts), committing to a disposable cell (dry_run supported). Failures return problems/next_action and change nothing. nanodevice.detect_commit commits flake traces as polygons from a precomputed traces.json, or runs live detection from a microscope image (needs cv2 + numpy).
| Tool | Kind | Params | Function |
|---|---|---|---|
nanodevice.detect_commit | local | cell, coordinate, dry_run, image, pixel_size_um, session, traces_path | Commit flake traces as polygons. traces_path (no extra deps) or image+pixel_size_um for live detection (needs cv2 + numpy). |
nanodevice.hallbar | local | cell, dry_run, route_layer, session, spacing_um, spec, writefield | Build + route + validate + commit one Hall bar in one call. |
Example.
nanodevice.hallbar cell="HB1" spec={
"bar_length_um":60, "bar_width_um":8, "contact_count":6,
"contact_width_um":4, "pad_size_um":40, "pitch_um":24, "gap_um":6
} route_layer="1/0" dry_run=true
nanodevice.detect_commit cell="FLAKE" traces_path="out/traces.json"
11 · Photonics (gdsfactory import / connect / reroute) device_photonics · 3 tools
Photonic circuit flow. Needs gdsfactory in the MCP interpreter. Two port sources, one interactive loop: photonics.import_gf takes a finished user gdsfactory script over into the loop in one call — device instances become real KLayout cells+instances, routed connections collapse to device-level nets, per-device port templates persist in the spec, and nets are routed by klink. port.harvest_blackbox (listed under ports_and_anchors — it derives Ports from live blackbox instance positions) is the other port source; re-run it after moving instances, then route. photonics.connect reads the latest N SENDs as port pairs, auto-names nets, persists, re-harvests, and routes with gdsfactory. photonics.reroute re-routes a cell after the user moved components (reads the persisted net table). Multi-port optical nets are not routed as stars — insert an explicit splitter/MMI/Y-branch first and route the resulting two-port nets.
| Tool | Kind | Params | Function |
|---|---|---|---|
photonics.connect | local | cell, radius_um, recent_sends*, route_layer, separation_um, stub_size_um, wg_layer, width_um | Connect ports the user just SENT: read latest N SENDs -> port pairs -> auto-name nets -> persist -> re-harvest -> route. |
photonics.import_gf | local | cell, component, port_layer, route, route_layer, script_path*, session | Take over a finished gdsfactory script: import device instances (batch RPC), collapse routed connections to nets, persist port templates + net table, route with klink. |
photonics.reroute | local | cell*, route_layer, session, stub_size_um, wg_layer | Re-route after the user moved components (reads the persisted net table). Multi-port optical nets need a splitter first. |
Example.
# 1) take over a finished script (c = build_mzi() inside it)
photonics.import_gf script_path="my_mzi.py" cell="MZI" route_layer="1/0"
# 2) drag a phase shifter in the KLayout GUI ... then:
photonics.reroute cell="MZI" # optics + metal redrawn together, your drag is kept
# ports can also come from the blackbox stub convention
port.harvest_blackbox cell="MZI" tags=["gc","mmi"] wg_layer="1/0" stub_size_um=0.5
photonics.connect recent_sends=4 cell="MZI" radius_um=10 separation_um=5
12 · L-Edit bridge (file-exchange RPC) bridge_ledit · 3 tools
Requires L-Edit running with the bridge macro loaded as SOURCE (example_template/ledit_bridge/ledit_bridge.cpp via Tools > Macro > Load Macro..., zero compile). Transport is a JSON file exchange under %LOCALAPPDATA%\klink\ledit_bridge\<namespace>; one namespace per L-Edit instance, only ONE L-Edit may hold a namespace at a time. ledit.status is discovery + handshake — call it first when anything misbehaves; errors name the exact fix. ledit.import_selection does a fresh GET of the user's current L-Edit selection into a new KLayout landing cell. ledit.push_cell pushes a flat KLayout cell back into L-Edit (append-only; use a fresh target cell to regenerate). Deeper T-Cell workflows live in the Python API klink.bridges.ledit — see the L-Edit Bridge guide.
| Tool | Kind | Params | Function |
|---|---|---|---|
ledit.import_selection | local | namespace, session, target_cell | Import the user's CURRENT L-Edit selection into a fresh KLayout landing cell (fresh GET every call). Capability-matched conversion; layers migrate by name + GDS number; non-convertibles are listed. |
ledit.push_cell | local | cell*, ledit_cell, namespace, session | Push a flat KLayout cell into L-Edit (box / path->wire / polygon). Sub-instances are counted, not silently dropped. L-Edit draw is append-only. |
ledit.status | local | namespace | Discovery and triage: macro liveness, whether a design is open, macro version/capabilities, current .tdb and cell. Call first when any ledit.* misbehaves. |
13 · Escape hatch (pya exec, events, recorder) escape_hatch · 9 tools
Prefer typed RPCs. exec.python runs raw pya for operations no typed RPC covers / debugging / compact one-offs (exec.reset clears its namespace); it still schedules recorder + layout-diff detection. events.* (channels/status/subscribe/unsubscribe) is the live event stream the bridge subscribes to for SEND memory — usually you read interaction.* instead. recorder.* (start/stop/status) generates a replay SCRIPT (not a literal RPC log); a bulk RPC may expand into replay actions. Check recorder.status before tests so you never clobber a user recording.
| Tool | Kind | Params | Function |
|---|---|---|---|
events.channels | rpc | — | List pushable event channels (NDJSON frames). |
events.status | rpc | — | Subscription + SignalHub diagnostics. |
events.subscribe | rpc | channels* | Subscribe to channels (unknown silently ignored). |
events.unsubscribe | rpc | channels | Unsubscribe; * drops all. |
exec.python | rpc | code*, protect_cellview, reset, result_mode, stderr_limit, stdout_limit | Run arbitrary Python on KLayout's Qt main thread. Pre-bound pya, mw, view, layout. State persists per connection (reset=true wipes). stdout/stderr captured. |
exec.reset | rpc | — | Clear the per-connection namespace. |
recorder.start | rpc | output_path | Begin recording into a replayable Python script. Idempotent. |
recorder.status | rpc | — | Recording state, event/action counts, output path. |
recorder.stop | rpc | output_path | Stop and write the script; returns stats + wrote. |
Example.
recorder.status # confirm nobody else is recording first
recorder.start
# ... some typed-RPC edits + manual GUI edits ...
recorder.stop # produces <name>.py and standalone <name>_pya.py
# only use the escape hatch when no typed RPC covers it
exec.python code="print(layout.top_cell().name); print(len(list(layout.each_cell())))"
recorder produces two files: <name>.py (KLinkClient-based replay) and <name>_pya.py (a standalone pya version runnable inside KLayout).