Files
kicad-zone-resistance/README.md
T
janik 06c62e04f8 Initial import: KiCad zone resistance plugin
DC/AC resistance, power dissipation, and via/injection-area currents of
copper zone fills. KiCad 10 IPC-API plugin (kicad-python/kipy):
multi-layer via-coupled FDM solver, multi-part terminals via User.1/User.2
marker layers, pads as contacts, uniform-injection and equipotential
contact models, per-foil skin effect, optional solder/copper buildup on
mask openings. 54-case test suite incl. exact analytic references.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:22:00 +07:00

135 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fill Resistance — KiCad 10 plugin
Computes the **DC resistance of copper zone fills** between two contacts,
**single- or multi-layer**: the chosen net's fills 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). 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)
1. **Enable the API server**: KiCad → Preferences → Plugins → check
*Enable KiCad API*.
2. **Check the interpreter path** on the same page: should be
`C:\Program Files\KiCad\10.0\bin\pythonw.exe` (after a 9→10 upgrade it
can point at KiCad 9).
3. **Deploy**:
```powershell
powershell -ExecutionPolicy Bypass -File deploy.ps1 # junction (dev)
powershell -ExecutionPolicy Bypass -File deploy.ps1 -Mode Copy
```
4. **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
1. 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 on `User.2`**
(marker layers, configurable via `ELECTRODE_POS_LAYER` /
`ELECTRODE_NEG_LAYER`), any number per side, axis-aligned;
- **pads** (real copper shape; through-hole pad contacts all layers,
SMD pad its own layer) — selected pads 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.
2. **Select the contacts**, click the **Fill Resistance** Ω button.
3. 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.
4. Read R / voltage drop / total power in the figure titles and status
bar. Outputs land in `<board dir>\fill_res_results\<timestamp>\`:
per-layer `1_raster_map` / `2_potential` / `3_current_density` /
`4_power_density` PNGs, `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 = 18` in `fill_resistance/config.py`. Vias are always
plated; capped vs uncapped does not change the layer-to-layer DC path
(the ≥5 µm cap sits over the hole mouth in parallel with the
annular-ring contact, not in series). A barrel passing an antipad
bridges the layers above/below with the full barrel length.
- Tracks and pad copper (other than the selected contacts) are **not**
part of the conductor model — zone fills + barrels only.
- **Solder buildup on mask openings** (dialog checkbox, **off by
default**; `INCLUDE_MASK_BUILDUP`): zones drawn on `F.Mask`/`B.Mask`
are treated as mask openings that collect `SOLDER_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.
- **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.
- 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 = 1` in 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) — suffixes `k`/`M` accepted.
**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.
- 5-point FDM per layer on an auto-sized shared grid (~500 k cells total
across layers by default). Direct sparse solve up to 700 k unknowns,
Jacobi-CG above. Discretization error typically ≲ 2 % at defaults —
halve the cell size and compare to judge convergence.
## Offline / development
Every run writes `geometry_dump.json`; re-solve without KiCad:
```powershell
.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:
```powershell
uv venv --python 3.11 .venv
uv pip install --python .venv\Scripts\python.exe kicad-python numpy scipy matplotlib pytest
.venv\Scripts\python.exe -m pytest tests -q # incl. exact analytic cases
.venv\Scripts\python.exe smoke\smoke_probe.py # IPC API probe vs live KiCad
.venv\Scripts\python.exe -m fill_resistance.board_io dump.json [NET] # extract only
```
## 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.