pcb_plan_placement
Auto-place or refine PCB component positions to shorten wirelength and remove overlaps; review proposed moves and quality metrics in a dry run before committing.
Instructions
Connectivity-driven auto-placement: shorten wirelength, keep components legal. Dry-run by default -- returns a proposed move list and quality metrics without touching the board.
A pure-Python solver run on the current board. It reads the compiled netlist and every component's real footprint bounding box, then improves positions to minimize half-perimeter wirelength (HPWL) while keeping components inside the board outline and free of same-layer overlaps.
Two solver cores are available via engine:
"refine"(default): a force-directed relaxation that nudges the board's CURRENT positions, then a legalization pass. Best for tidying an already-placed board."construct": a from-scratch constructor that sizes the placement, seeds parts at connectivity-weighted centroids, chooses orientations, legalizes, and runs a short cooled polish on a full multi-term objective (wirelength, overlap, board-edge, decoupling proximity, connector banding, thermal). Best for an unplaced or badly scrambled board. The full per-term objective is always returned underobjective_report.
With optimize_rotation (default on) it also picks each
eligible part's orientation (0/90/180/270) to point its pins at
the nets they connect -- the classic auto-place win for 2-pin
passives. This needs per-pin geometry, so the tool also reads the
board's pads and maps them to components; orientation is only
considered for top-side parts currently sitting at an orthogonal
angle (others keep their rotation).
Workflow: call once with apply=False (default), read the
hpwl_improvement_pct and overlap_pairs_after, eyeball the
moves, then call again with apply=True to commit them in
one pcb.batch_move_components transaction. Components on
opposite layers may share an X/Y footprint (they cannot
physically collide).
Seeding: by default the solver refines the board's CURRENT
positions (small, sensible nudges). Pass reseed_grid=True for
a from-scratch re-place (ignores current positions) -- use only
on an unplaced or badly-scrambled board.
Args:
designators: If given, ONLY these components are moved; every
other placed component is held fixed but still attracts
its nets and blocks overlaps. If omitted, all components
are candidates (minus fixed).
fixed: Designators to pin in place (connectors, mounting
holes, anything already positioned). Naming-agnostic --
you choose which; the tool does not guess by prefix.
region: Placement bounds {x1, y1, x2, y2} in mils. If
omitted, the board outline's bounding rectangle is used.
iterations: Global-placement relaxation steps (default 400).
More iterations = better convergence, longer runtime.
grid_mils: Snap grid for final positions (default 5).
clearance_mils: Extra copper-to-copper breathing room added
to each pair's half-extents (default 15).
max_net_fanout: Skip nets touching more than this many
components (default 0 = keep all). Set e.g. 20 to ignore
power/ground planes that do not usefully guide placement.
exclude_nets: Explicit net names to ignore (e.g. ["GND"]).
critical_nets: Net names to treat as placement-critical (e.g. a
clock, a high-speed pair, a sensitive analog feedback line).
Their pull is scaled by critical_weight so the solver
keeps their endpoints close and shortens those traces first,
at a small cost to ordinary nets. You (the planner) decide
which nets matter; the tool does not guess by name.
critical_weight: Multiplier applied to critical_nets pull
(default 3.0, clamped to [1, 20]). Higher = more aggressive
shortening of the critical nets versus the rest.
edge_parts: {refdes: band} mapping parts that must sit on a
board EDGE to which edge -- "L"/"R"/"T"/"B"
(left/right/top/bottom). The solver seats each against that
edge (centred on the free axis) and holds it there. Use for
connectors, mounting holes, USB/headers -- anything needing
cable or panel access. You (the planner) decide which; the
tool does not guess by designator prefix.
keepout_groups: {refdes: group} mixed-signal separation tags.
Parts in DIFFERENT groups are pushed apart (the
industrial-engineering "undesirable adjacency" relationship)
-- segregate a noisy switching / digital section from a
sensitive analog / RF one. The group string is opaque (only
equality matters); you (the planner) assign the domains
semantically. construct engine only. Separating the groups
usually also shortens wirelength by de-interleaving them.
match_groups: {refdes: group} matched-pair keep-together tags.
Parts in the SAME group are pulled ADJACENT even when they
share no net -- the absolutely-necessary-adjacency relationship
/ analog common-centroid matching (a differential pair's two
input resistors, a current mirror, a matched array). HPWL only
co-locates parts that share a net; this co-locates matched ones
that don't. construct engine only.
match_roles: {refdes: role} common-centroid sub-device labels
WITHIN a match_group (e.g. A/B for the two matched
halves of a cross-quad or interdigitated array). The optimiser
drives each role's centroid onto a common point so a linear
process/thermal gradient cancels -- it picks the balanced
ABBA/ABAB arrangement that match_groups alone (which only
keeps the cells compact) cannot distinguish from an unbalanced
AABB. Only meaningful with >= 2 roles per group and >= 2 cells
per role; needs match_groups set too. construct only.
plan_json: The DesignPlan (JSON string or dict) you authored. When
given, the placer auto-derives matched-pair match_groups
from differential nets and analog/digital keepout_groups
from the plan's net roles, and MERGES them under the explicit
match_groups / keepout_groups args (explicit wins per
refdes). No-op for a plan with no differential / mixed-signal
structure. What was inferred is echoed in auto_constraints.
reseed_grid: Re-place from a fresh grid instead of refining
current positions (default False).
optimize_rotation: Also choose part orientation to shorten
pin-level wirelength (default True). Set False to keep
every part's current rotation.
engine: "refine" (default, nudge current positions) or
"construct" (from-scratch placement). "construct"
implies a from-scratch re-place regardless of
reseed_grid.
restarts: "construct" engine only. When > 1, run that many
seeded restarts and keep the lowest-objective legal
placement (variance reduction at a cost linear in
restarts). Default 1 (single deterministic run).
render_png: Optional file path. When set, also render a preview
image of the proposed placement (board outline, courtyards,
net stars) to that path and return it as preview_png
(offline, matplotlib). Rendering never breaks the move data;
failures surface as preview_error.
apply: When True, commit the moves to the board. Default
False (dry-run).
Returns:
Dict with:
- dry_run: bool (True unless apply and moves exist)
- engine: the solver core used
- component_count, movable_count, fixed_count
- net_count (nets used after filtering)
- pin_count (pads mapped to components for rotation)
- summary: a one-line plain-language assessment synthesising
legality, utilization, signal routability, decoupling, and the
suggested board size -- read this first to judge the placement
- hpwl_before, hpwl_after, hpwl_improvement_pct
- overlap_pairs_before, overlap_pairs_after
- objective_report: every objective term un-weighted
(hpwl, via, cong, clear, edge,
decap, conn, therm) plus weighted_total,
legal, and utilization for the proposed layout
- net_length_report: longest_nets (the few nets with
the largest physical bounding span in mils -- the routing
risk, and the candidates to mark critical_nets) plus
critical_net_spans echoing the achieved span of each
net you flagged critical, so you can confirm it paid off
- ratsnest: routability / via-pressure indicator from the
straight-line MST of each net. signal_crossings (rails
above signal_fanout_cap pins excluded, since planes carry
no signal via) predicts routing difficulty -- different-net
MST edges that cross need a layer change; total_crossings
is the conservative all-net count
- decoupling_report: the engine's structural decoupling
analysis -- a list of {decap, ic, distance_mils} (worst
first) naming which cap decouples which IC (found on the
connectivity graph, never by reference) and how close it
landed to the served power pin. Empty when no decoupling is
structurally identifiable
- suggested_board: for the construct engine, the board
rectangle it sized and tightened to the placement
({x1, y1, x2, y2, width, height} in mils) -- the "best
PCB size" recommendation. null for the refine engine,
which works inside the existing outline
- moved_count, rotated_count and moves (each
{designator, from: {x, y, rotation}, to: {x, y, rotation}} in mils/degrees; to.rotation is null
when the orientation was unchanged)
- region used, notes from the solver, and
apply_result when apply=True.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apply | No | ||
| fixed | No | ||
| engine | No | refine | |
| region | No | ||
| restarts | No | ||
| grid_mils | No | ||
| plan_json | No | ||
| edge_parts | No | ||
| iterations | No | ||
| render_png | No | ||
| designators | No | ||
| match_roles | No | ||
| reseed_grid | No | ||
| exclude_nets | No | ||
| match_groups | No | ||
| critical_nets | No | ||
| clearance_mils | No | ||
| keepout_groups | No | ||
| max_net_fanout | No | ||
| critical_weight | No | ||
| optimize_rotation | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||