- THT pad copper is now part of the conductor: the exact pad outline (incl. oblong/custom shapes) is fetched from KiCad once per pad and stamped onto every included layer (the outer shape stands in for inner rings). Annular rings bridge antipads, and joints land on real copper instead of only pour coverage. - The internal lead conductor is modeled in every solder-filled hole: a cylinder of drill - THT_LEAD_CLEARANCE_MM (0.25 fab rule) with THT_LEAD_RHO_OHM_M (copper default; config for brass/steel leads), in parallel with the solder annulus and the plating. - Drill mouths of THT pads: populated pads keep conducting mouth copper (stands in for the solder plug - conservative, the plug is worth ~200 um of copper equivalent); DNP pad holes are cut open on every layer like uncapped via mouths. - Oblong pads: the coat uses the exact pad shape; the lead cone tapers within the inscribed circle (new pad_min_nm on ViaLink/Electrode) so the long axis is not overstated sideways. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 KiB
Fill Resistance — KiCad 10 plugin
Computes the DC or AC resistance of copper zone fills and traces between two contacts, single- or multi-layer: the chosen net's fills (teardrops included) and tracks on the selected copper layers are solved as coupled finite-difference sheets linked by the net's via and through-hole-pad barrels (18 µm plating, configurable). At a user-set frequency the exact 1D foil/barrel skin-effect correction is applied (AC results are a rigorous lower bound — see Model & limits). Shows per-layer rasterized maps, potential, current density, and power density, reports per-via currents (via ampacity!) and total dissipation at a selectable test current. PNGs + a text summary are saved per run.
Uses the KiCad IPC API (kicad-python / kipy), not the deprecated
SWIG API. Requires KiCad 10.0.1+.
Setup (one-time)
- Enable the API server: KiCad → Preferences → Plugins → check Enable KiCad API.
- Check the interpreter path on the same page: should point at the
KiCad 10 Python, e.g.
C:\Program Files\KiCad\10.0\bin\pythonw.exeon Windows or/usr/bin/python3on Linux (after a 9→10 upgrade it can point at KiCad 9). - Deploy (dev checkout; end users install the PCM zip instead, see
Packaging):
Linux / macOS (also works on Windows with developer mode):
powershell -ExecutionPolicy Bypass -File deploy.ps1 # junction (dev) powershell -ExecutionPolicy Bypass -File deploy.ps1 -Mode Copypython3 tools/deploy.py # symlink (dev) python3 tools/deploy.py --copy - Restart KiCad; first load builds the plugin venv (numpy, scipy, matplotlib, PySide6 — takes minutes; the Ω button appears when done). If stuck: Preferences → Plugins → Recreate Plugin Environment.
Usage
- Mark the current-injection terminals. Each terminal may have
multiple parts (all merged into one externally-bonded contact):
- V+ rectangles on
User.1, V− rectangles onUser.2(marker layers, configurable viaELECTRODE_POS_LAYER/ELECTRODE_NEG_LAYER), any number per side, axis-aligned; - pads and vias (SMD pad: real copper shape on its own layer; through-hole pads and vias become barrel contacts — the current enters at the drill wall on every spanned layer, see below) — selected pads/vias fill a side that has no rectangles;
- legacy: exactly 2 selected contacts with no marker rectangles still works; empty selection scans the whole board's marker layers.
- V+ rectangles on
- Select the contacts, click the Fill Resistance Ω button.
- In the dialog, pick the net (defaults to the selected pad's net), check the layers to include, set each contact's layer scope ("All selected layers" = bolted-lug/through contact), the test current, and optionally a grid cell size. Multiple layers are coupled through the net's via/pad barrels automatically.
- Read R / voltage drop / total power in the figure titles and status
bar. Outputs land in
<board dir>\fill_res_results\<timestamp>\: per-layer1_raster_map/2_potential/3_current_density/4_power_densityPNGs,summary.txt(incl. the busiest vias with per-via current and dissipation, and the current through each injection area — computed flux with the equipotential model, prescribed area share with the uniform model),geometry_dump.json.
Model & limits
- Sheet model per layer: R□ = ρ/t, ρ = 1.68e-8 Ωm (20 °C), t from the board's physical stackup. Layer z-positions from the stackup drive the barrel lengths.
- Via/pad barrels: thin-wall annulus, R = ρ·L/(π·d·t_plating),
VIA_PLATING_UM = 18infill_resistance/config.py. Vias are always plated. Each via also contributes its ring/pad copper (a full-thickness disc of the pad diameter on every spanned layer) and its drill mouth, area-weighted per cell: with the "vias filled + capped" checkbox (default on,VIAS_CAPPED) the mouth carries a thin copper cap (CAP_PLATING_UM = 15, fab spec) on the outer layers and is an open hole on inner layers; unchecked, mouths are open holes everywhere. The fab caps only small vias: drills above the dialog's "capped up to drill" threshold (defaultCAP_MAX_DRILL_MM = 0.5) keep open mouths even with capping selected. Layer-to-layer the cap never matters at DC (it is in parallel with the annular-ring contact, not in series) — the checkbox only affects in-plane conduction across outer-layer mouths. Sub-cell mouths scale their cells' sheet conductance by the true covered fraction (4×4 supersampling), so coarse grids see the correct small perturbation instead of a whole-cell hole. Barrels are gathered in single-layer runs too (drill mouths perforate a lone plane). THT pads are fully modeled: their exact copper shapes (incl. oblong pads, fetched from KiCad; the outer shape stands in for inner rings) are stamped onto every included layer, and every populated pad carries its full soldered joint on its SOLDER side (opposite the component; the component-side pad face stays bare): the hole holds the component lead (a cylinder of drill −THT_LEAD_CLEARANCE_MM, resistivityTHT_LEAD_RHO_OHM_M, copper by default — raise it for brass/steel leads) plus solder in the remaining annulus, both in parallel with the plating; the mouth copper stays conducting (it stands in for the plug — conservative, the plug is worth far more than the foil); the pad face gets the average-thickness solder coat (exact pad shape) and the protruding-lead cone (see barrel contacts below; on oblong pads the cone tapers within the inscribed circle). Whether a hole is a via or a THT pad, the owning footprint's side, and its Do not populate flag are all read from KiCad — DNP pads get an open hole and a plating-only barrel, no joint. At f > 0 the thickness scaling is applied multiplicatively to the skin-corrected sheet conductance (approximation). Per layer a barrel attaches to the fill cell under it, or to the nearest copper cell within the pad footprint plus one grid cell — fills joined by thermal-relief spokes still connect; wider antipads do not, and the barrel bridges the layers above/below with the full barrel length. Barrels that reach fill on fewer than two layers carry no current and are reported. - The net's traces (straight and arc tracks, exact outline polygons
incl. rounded ends) conduct together with the fills — dialog checkbox,
on by default (
INCLUDE_TRACKS). Traces narrower thanTRACK_1D_FACTOR(3) grid cells are modeled as exact 1D resistor chains along their centerline — true arc length per link, so their series resistance carries no discretization error and no cell-size tuning is needed for thin traces. 1D-modeled traces show potential, power density, and |J| (the true in-trace density from the link currents, |ΔV|/(ρ·Δl)). THT pad copper is part of the conductor (exact shapes, see above); SMD pad copper other than the selected contacts is still not. - Solder buildup on mask openings (dialog checkbox, off by
default;
INCLUDE_MASK_BUILDUP): zones drawn onF.Mask/B.Maskare treated as mask openings that collectSOLDER_THICKNESS_UM(50 µm) of solder on the exposed pour, plus an optional user-defined added copper thickness (dialog field, e.g. a soldered busbar/wire). The sheet conductance there becomes t_Cu/ρ_Cu + t_solder/ρ_solder + t_extra/ρ_Cu (SAC305 ρ = 1.32e-7 Ωm: 50 µm solder ≈ 6.4 µm copper); interface faces use harmonic-mean conductances. Buildup areas render tin-gray on the raster map; |J| in them is referenced to the conductance-equivalent copper thickness. - Barrel contacts: a selected via or through-hole pad injects at
the drill-wall ring on every layer the barrel spans — the current
physically enters through the lead/wire soldered into the hole, so
the spreading resistance across the pad and surrounding pour is part
of the result (both contact models; verified against
R = ρ/(π·t)·acosh(d/2a) for two circular contacts on a sheet). A
soldered THT joint additionally assumes the hole is filled with
solder (core in parallel with the plating) and the pad face on
the solder side carries an average-thickness solder coat
(
SOLDER_THICKNESS_UM, 50 µm) over the modeled copper under the pad shape — the solder side is the side opposite the component (taken from the owning footprint; assumedB.Cuif it cannot be found), and the component-side pad face stays bare. There the clipped lead protrudesTHT_LEAD_PROTRUSION_MM(1.5 mm, 0 = off) and a solder cone wraps it: full protrusion height at the drill wall, tapering linearly to zero at the pad edge, applied as extra conduction-equivalent copper per cell. The tall solder column at the wall pulls the joint vicinity to lead potential — equivalent to extending the barrel wall vertically — while the taper carries the radial spreading. To model a probe pressed onto the pad face instead, draw a marker rectangle over the pad. - Contact models (dialog /
CONTACT_MODEL): default uniform injection — a conductor pressed on top feeds the current orthogonally with uniform surface density, so |J| ramps across the contact area (R = ΔV̄/I from area-averaged terminal potentials); or equipotential — ideal bonded lug (Dirichlet). The two bracket a real contact: R_equipotential ≤ R_real ≤ R_uniform. If the selected fills form several disconnected copper groups that each touch both terminals (e.g. planes joined only through the bolted lugs), only the equipotential model is well-defined; the uniform model stops with an error instead of prescribing an arbitrary split. - Fields are reported at the dialog's test current; power scales with I².
- Skin effect (f > 0): per-layer effective sheet resistance from the
exact 1D foil-diffusion solution
Zs = τρ·coth(τt),τ = (1+j)/δ(SKIN_SIDES = 1in config: plane facing a return plane;2= isolated foil), and the analogous correction for the 18 µm barrel wall. Enter one frequency per run (e.g. a switching harmonic, with its RMS amplitude as the test current) — suffixesk/Maccepted. Caveat: only through-thickness crowding is modeled. Lateral (proximity-effect) redistribution needs a magneto-quasistatic solver and is not captured — since the resistance-driven distribution is the minimum-dissipation one, AC results are a rigorous lower bound. Rule of thumb for 70 µm foil: skin is negligible below ~300 kHz (δ = 173 µm at 142 kHz), ~+11 % at 1 MHz. At f > 0 the |J| maps are referenced to the skin-reduced conduction-equivalent thickness t/(R_AC/R_DC) — the density in the copper that actually conducts — not the geometric foil thickness. - 5-point FDM per layer on an auto-sized shared grid (~2 M fine cells with the uniform grid; ~8 M with the adaptive grid, whose unknown count no longer scales with them). Direct sparse solve up to 500 k unknowns, AMG-preconditioned CG (pyamg) above — Jacobi-CG if pyamg is missing. Discretization error typically ≲ 2 % at defaults — halve the cell size and compare to judge convergence.
- Adaptive cells (dialog checkbox, on by default;
ADAPTIVE_CELLS): solves on a 2:1-balanced quadtree — fine cells at copper boundaries, electrodes, traces, via mouths and buildup, blocks up toADAPTIVE_MAX_CELL_UM(2 mm) in plane interiors (ADAPTIVE_GUARDsets the clearance a block needs to grow). The minimum element size is the grid cell size itself (auto / dialog /CELL_UM_OVERRIDE); the uniform limit reproduces the normal grid exactly. Large speed/memory wins on big pours. The raw coarse–fine interface flux bias (~0.5–2 % low) is removed by a deferred-correction pass (ADAPTIVE_CORRECTION_PASSES, default 1: reconstruct leaf gradients, move the tangential term to the RHS, re-solve on the reused factorization/AMG hierarchy) — measured residual deviation from the uniform grid ≲ 0.03 %, with the power-balance identity intact. All fields are expanded back to the fine grid for the maps and reports.
Offline / development
Every run writes geometry_dump.json; re-solve without KiCad:
.venv\Scripts\python.exe -m fill_resistance.standalone dump.json `
[--current 40] [--cell-um 50] [--layers F.Cu,In1.Cu] [--no-show] `
[--out DIR] [--force-iterative]
Dev environment, tests, headless extraction (Windows shown; on
Linux/macOS use .venv/bin/python):
uv venv --python 3.11 .venv
uv pip install --python .venv\Scripts\python.exe kicad-python numpy scipy pyamg matplotlib pytest
.venv\Scripts\python.exe -m pytest tests -q # incl. exact analytic cases
.venv\Scripts\python.exe tools\api_probe.py # IPC API probe vs live KiCad
.venv\Scripts\python.exe -m fill_resistance.board_io dump.json [NET] # extract only
Packaging / publishing
python tools/build_package.py builds the PCM addon zip in dist/
(installable right away via Plugin and Content Manager → Install from
File) plus dist/metadata-registry.json with the SHA-256 and sizes
filled in. To publish: upload the zip to a release, set download_url
(and the homepage resource in metadata.json), then submit the
registry copy as packages/th.co.b4l.fill-resistance/metadata.json in a
merge request to https://gitlab.com/kicad/addons/metadata. Icons are
regenerated with python tools/gen_icons.py.
License
GPL-3.0-or-later — see LICENSE.
Troubleshooting
- No toolbar button: venv still building (wait), or build failed → Recreate Plugin Environment; check the interpreter path (setup 2).
- "Could not connect to KiCad's IPC API": API server not enabled, or KiCad not running (no headless mode in KiCad 10).
- "KiCad is busy": a modal dialog is open in KiCad — close it, rerun.
- Windows don't appear: they may open behind KiCad (raised best-effort); PNGs are always saved regardless.
- Result seems too low/high: remember the model is fills + barrels only, with ideal contacts; measure electrode-to-electrode.