uv dev environment, README writing pass, publish hygiene
Build PCM package / build (push) Successful in 8s
Build PCM package / build (push) Successful in 8s
- pyproject.toml + uv.lock: uv-managed dev environment (uv sync / uv run pytest). requirements.txt stays: KiCad builds the plugin's runtime venv from it and the PCM zip packages it. - README: em-dash and run-on cleanup (34 -> 22, the rest deliberate), split the 120-word THT solder-joint sentence, fix the documented ADAPTIVE_MAX_CELL_UM value (2 mm -> 1 mm, config has 1000 um), fix the *Packaging / publishing* cross-reference, dev sections now use uv sync / uv run. - LLM disclaimer: "most commits" carry the trailer (32 of 39), figures claim now excepts the hand-drawn hole cross-section, reference the UT3513+ measured-vs-computed validation. - .gitignore: local AI-tooling artifacts; deploy scripts exclude pyproject.toml/uv.lock; error-figure title punctuation aligned with the other window titles.
This commit is contained in:
@@ -3,3 +3,11 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
# local AI-tooling artifacts, never publish
|
||||||
|
.claude/
|
||||||
|
.claude-flow/
|
||||||
|
.swarm/
|
||||||
|
.mcp.json
|
||||||
|
CLAUDE.md
|
||||||
|
ruvector.db
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ between two contacts, **single- or multi-layer**: the chosen net's fills
|
|||||||
solved as coupled finite-difference sheets linked by the net's **via
|
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
|
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
|
skin-effect correction is applied (AC results are a rigorous lower
|
||||||
bound — see *Model & limits*). Shows per-layer rasterized maps,
|
bound; see *Model & limits*). Shows per-layer rasterized maps,
|
||||||
potential, current density, and **power density**, reports **per-via
|
potential, current density, and **power density**, and reports **per-via
|
||||||
currents** (via ampacity!) and total dissipation at a **selectable test
|
currents** (via ampacity!) and total dissipation at a **selectable test
|
||||||
current**. PNGs + a text summary are saved per run.
|
current**. PNGs + a text summary are saved per run.
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ current**. PNGs + a text summary are saved per run.
|
|||||||
*Real output on a synthetic two-layer net: current from a soldered
|
*Real output on a synthetic two-layer net: current from a soldered
|
||||||
THT-pad contact (V+, injected at the drill-wall ring) squeezes past a
|
THT-pad contact (V+, injected at the drill-wall ring) squeezes past a
|
||||||
notch in the F.Cu pour, transfers through the stitching-via field into
|
notch in the F.Cu pour, transfers through the stitching-via field into
|
||||||
the B.Cu pour and leaves at the V− lug — per-via currents and the
|
the B.Cu pour and leaves at the V− lug. Per-via currents and the
|
||||||
hottest via are reported.*
|
hottest via are reported.*
|
||||||
|
|
||||||

|

|
||||||
@@ -35,7 +35,7 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
on Windows or `/usr/bin/python3` on Linux (after a 9→10 upgrade it
|
on Windows or `/usr/bin/python3` on Linux (after a 9→10 upgrade it
|
||||||
can point at KiCad 9).
|
can point at KiCad 9).
|
||||||
3. **Deploy** (dev checkout; end users install the PCM zip instead, see
|
3. **Deploy** (dev checkout; end users install the PCM zip instead, see
|
||||||
*Packaging*):
|
*Packaging / publishing*):
|
||||||
```powershell
|
```powershell
|
||||||
powershell -ExecutionPolicy Bypass -File deploy.ps1 # junction (dev)
|
powershell -ExecutionPolicy Bypass -File deploy.ps1 # junction (dev)
|
||||||
powershell -ExecutionPolicy Bypass -File deploy.ps1 -Mode Copy
|
powershell -ExecutionPolicy Bypass -File deploy.ps1 -Mode Copy
|
||||||
@@ -52,13 +52,13 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Mark the current-injection terminals. Each terminal may have
|
1. Mark the current-injection terminals. Each terminal may have
|
||||||
**multiple parts** (all merged into one externally-bonded contact):
|
**multiple parts** (all merged into one externally bonded contact):
|
||||||
- **V+ rectangles on `User.1`**, **V− rectangles on `User.2`**
|
- **V+ rectangles on `User.1`**, **V− rectangles on `User.2`**
|
||||||
(marker layers, configurable via `ELECTRODE_POS_LAYER` /
|
(marker layers, configurable via `ELECTRODE_POS_LAYER` /
|
||||||
`ELECTRODE_NEG_LAYER`), any number per side, axis-aligned;
|
`ELECTRODE_NEG_LAYER`), any number per side, axis-aligned;
|
||||||
- **pads and vias** (SMD pad: real copper shape on its own layer;
|
- **pads and vias** (SMD pad: real copper shape on its own layer;
|
||||||
through-hole pads and vias become **barrel contacts** — the current
|
through-hole pads and vias become **barrel contacts**: the current
|
||||||
enters at the drill wall on every spanned layer, see below) —
|
enters at the drill wall on every spanned layer, see below);
|
||||||
selected pads/vias fill a side that has no rectangles;
|
selected pads/vias fill a side that has no rectangles;
|
||||||
- legacy: exactly 2 selected contacts with no marker rectangles still
|
- legacy: exactly 2 selected contacts with no marker rectangles still
|
||||||
works; empty selection scans the whole board's marker layers.
|
works; empty selection scans the whole board's marker layers.
|
||||||
@@ -93,7 +93,7 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
dialog's **"capped up to drill"** threshold (default
|
dialog's **"capped up to drill"** threshold (default
|
||||||
`CAP_MAX_DRILL_MM = 0.5`) keep open mouths even with capping
|
`CAP_MAX_DRILL_MM = 0.5`) keep open mouths even with capping
|
||||||
selected. Layer-to-layer the cap never matters at DC (it is in
|
selected. Layer-to-layer the cap never matters at DC (it is in
|
||||||
parallel with the annular-ring contact, not in series) — the checkbox
|
parallel with the annular-ring contact, not in series), so the checkbox
|
||||||
only affects in-plane conduction across outer-layer mouths. Sub-cell
|
only affects in-plane conduction across outer-layer mouths. Sub-cell
|
||||||
mouths scale their cells' sheet conductance by the true covered
|
mouths scale their cells' sheet conductance by the true covered
|
||||||
fraction (4×4 supersampling), so coarse grids see the correct small
|
fraction (4×4 supersampling), so coarse grids see the correct small
|
||||||
@@ -103,18 +103,18 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
oblong pads, fetched from KiCad; the outer shape stands in for inner
|
oblong pads, fetched from KiCad; the outer shape stands in for inner
|
||||||
rings) are stamped onto every included layer, and every **populated**
|
rings) are stamped onto every included layer, and every **populated**
|
||||||
pad carries its full **soldered joint** on its SOLDER side (opposite
|
pad carries its full **soldered joint** on its SOLDER side (opposite
|
||||||
the component; the component-side pad face stays bare): the hole
|
the component; the component-side pad face stays bare). The hole
|
||||||
holds the **component lead** (a cylinder of drill −
|
holds the **component lead** (a cylinder of drill −
|
||||||
`THT_LEAD_CLEARANCE_MM`, resistivity `THT_LEAD_RHO_OHM_M`, copper by
|
`THT_LEAD_CLEARANCE_MM`, resistivity `THT_LEAD_RHO_OHM_M`, copper by
|
||||||
default — raise it for brass/steel leads) **plus solder** in the
|
default; raise it for brass/steel leads) **plus solder** in the
|
||||||
remaining annulus, both in parallel with the plating; the mouth
|
remaining annulus, both in parallel with the plating. The mouth
|
||||||
copper stays conducting (it stands in for the plug — conservative,
|
copper stays conducting: it stands in for the solder plug, which is
|
||||||
the plug is worth far more than the foil); the pad face gets the
|
worth far more than the foil, so this is conservative. The pad face
|
||||||
average-thickness solder coat (exact pad shape) and the
|
gets the average-thickness solder coat (exact pad shape) and the
|
||||||
protruding-lead cone (see barrel contacts below; on oblong pads 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
|
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**
|
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
|
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
|
a plating-only barrel, no joint. At f > 0 the thickness scaling is
|
||||||
applied multiplicatively to the skin-corrected sheet conductance
|
applied multiplicatively to the skin-corrected sheet conductance
|
||||||
(approximation). Per layer a barrel attaches to
|
(approximation). Per layer a barrel attaches to
|
||||||
@@ -194,7 +194,7 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
(`SKIN_SIDES = 1` in config: plane facing a return plane; `2` =
|
(`SKIN_SIDES = 1` in config: plane facing a return plane; `2` =
|
||||||
isolated foil), and the analogous correction for the 18 µm barrel wall.
|
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
|
Enter one frequency per run (e.g. a switching harmonic, with its RMS
|
||||||
amplitude as the test current) — suffixes `k`/`M` accepted.
|
amplitude as the test current); suffixes `k`/`M` are accepted.
|
||||||
**Caveat:** only through-thickness crowding is modeled. Lateral
|
**Caveat:** only through-thickness crowding is modeled. Lateral
|
||||||
(proximity-effect) redistribution needs a magneto-quasistatic solver
|
(proximity-effect) redistribution needs a magneto-quasistatic solver
|
||||||
and is not captured — since the resistance-driven distribution is the
|
and is not captured — since the resistance-driven distribution is the
|
||||||
@@ -206,15 +206,15 @@ SWIG API. Requires KiCad **10.0.1+**.
|
|||||||
not the geometric foil thickness.
|
not the geometric foil thickness.
|
||||||
- 5-point FDM per layer on an auto-sized shared grid (~2 M fine cells
|
- 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
|
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
|
count no longer scales with the fine-cell count). Direct sparse solve
|
||||||
unknowns, AMG-preconditioned CG (pyamg) above — Jacobi-CG if pyamg is
|
up to 500 k unknowns, AMG-preconditioned CG (pyamg) above (Jacobi-CG
|
||||||
missing. Discretization error typically ≲ 2 % at defaults — halve the
|
if pyamg is missing). Discretization error typically ≲ 2 % at
|
||||||
cell size and compare to judge convergence.
|
defaults; halve the cell size and compare to judge convergence.
|
||||||
- **Adaptive cells** (dialog checkbox, **on by default**;
|
- **Adaptive cells** (dialog checkbox, **on by default**;
|
||||||
`ADAPTIVE_CELLS`):
|
`ADAPTIVE_CELLS`):
|
||||||
solves on a 2:1-balanced quadtree — fine cells at copper boundaries,
|
solves on a 2:1-balanced quadtree — fine cells at copper boundaries,
|
||||||
electrodes, traces, via mouths and buildup, blocks up to
|
electrodes, traces, via mouths and buildup, blocks up to
|
||||||
`ADAPTIVE_MAX_CELL_UM` (2 mm) in plane interiors (`ADAPTIVE_GUARD`
|
`ADAPTIVE_MAX_CELL_UM` (1 mm) in plane interiors (`ADAPTIVE_GUARD`
|
||||||
sets the clearance a block needs to grow). The **minimum element size
|
sets the clearance a block needs to grow). The **minimum element size
|
||||||
is the grid cell size itself** (auto / dialog / `CELL_UM_OVERRIDE`);
|
is the grid cell size itself** (auto / dialog / `CELL_UM_OVERRIDE`);
|
||||||
the uniform limit reproduces the normal grid exactly. Large
|
the uniform limit reproduces the normal grid exactly. Large
|
||||||
@@ -246,20 +246,20 @@ accordingly more trustworthy than absolute numbers.
|
|||||||
Every run writes `geometry_dump.json`; re-solve without KiCad:
|
Every run writes `geometry_dump.json`; re-solve without KiCad:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.venv\Scripts\python.exe -m fill_resistance.standalone dump.json `
|
uv run python -m fill_resistance.standalone dump.json `
|
||||||
[--current 40] [--cell-um 50] [--layers F.Cu,In1.Cu] [--no-show] `
|
[--current 40] [--cell-um 50] [--layers F.Cu,In1.Cu] [--no-show] `
|
||||||
[--out DIR] [--force-iterative]
|
[--out DIR] [--force-iterative]
|
||||||
```
|
```
|
||||||
|
|
||||||
Dev environment, tests, headless extraction (Windows shown; on
|
Dev environment, tests, headless extraction — [uv](https://docs.astral.sh/uv/)
|
||||||
Linux/macOS use `.venv/bin/python`):
|
manages the venv from `pyproject.toml`/`uv.lock` (`requirements.txt`
|
||||||
|
stays: KiCad builds the plugin's runtime venv from it):
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
uv venv --python 3.11 .venv
|
uv sync # one-time env setup
|
||||||
uv pip install --python .venv\Scripts\python.exe kicad-python numpy scipy pyamg matplotlib pytest
|
uv run pytest -q # incl. exact analytic cases
|
||||||
.venv\Scripts\python.exe -m pytest tests -q # incl. exact analytic cases
|
uv run python tools/api_probe.py # IPC API probe vs live KiCad
|
||||||
.venv\Scripts\python.exe tools\api_probe.py # IPC API probe vs live KiCad
|
uv run python -m fill_resistance.board_io dump.json [NET] # extract only
|
||||||
.venv\Scripts\python.exe -m fill_resistance.board_io dump.json [NET] # extract only
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Packaging / publishing
|
## Packaging / publishing
|
||||||
@@ -272,7 +272,7 @@ filled in. To publish: upload the zip to a release, set `download_url`
|
|||||||
registry copy as `packages/th.co.b4l.fill-resistance/metadata.json` in a
|
registry copy as `packages/th.co.b4l.fill-resistance/metadata.json` in a
|
||||||
merge request to <https://gitlab.com/kicad/addons/metadata>. Icons are
|
merge request to <https://gitlab.com/kicad/addons/metadata>. Icons are
|
||||||
regenerated with `python tools/gen_icons.py`; the README figures in
|
regenerated with `python tools/gen_icons.py`; the README figures in
|
||||||
`docs/img/` with `.venv\Scripts\python.exe tools\gen_readme_figs.py`
|
`docs/img/` with `uv run python tools/gen_readme_figs.py`
|
||||||
(real solver output on small synthetic boards, plus the hand-drawn
|
(real solver output on small synthetic boards, plus the hand-drawn
|
||||||
hole cross-section).
|
hole cross-section).
|
||||||
|
|
||||||
@@ -294,18 +294,20 @@ GPL-3.0-or-later — see [LICENSE](LICENSE).
|
|||||||
|
|
||||||
## LLM disclaimer
|
## LLM disclaimer
|
||||||
|
|
||||||
This plugin was developed with an LLM — Anthropic's **Claude** (Claude
|
This plugin was developed with an LLM: Anthropic's **Claude** (Claude
|
||||||
Code, model Claude Fable 5). The physics model, solver, tests, tooling
|
Code, model Claude Fable 5). The physics model, solver, tests, tooling
|
||||||
and this documentation (including the figures, which are generated by
|
and this documentation (including the figures; all but the hand-drawn
|
||||||
the solver itself) were written by the model, feature by feature, under
|
hole cross-section are generated by the solver itself) were written by
|
||||||
human direction and review (janik / B4L); commits carry a
|
the model, feature by feature, under human direction and review
|
||||||
`Co-Authored-By: Claude` trailer.
|
(janik / B4L); most commits carry a `Co-Authored-By: Claude` trailer.
|
||||||
|
|
||||||
What keeps this honest: the test suite pins the numerics to exact
|
What keeps this honest: the test suite pins the numerics to exact
|
||||||
analytic references (strip and annulus resistances, the acosh spreading
|
analytic references (strip and annulus resistances, the acosh spreading
|
||||||
resistance of two circular contacts, skin-effect limits, power-balance
|
resistance of two circular contacts, skin-effect limits, power-balance
|
||||||
identities) and to convergence/regression checks — run it with
|
identities) and to convergence/regression checks; run it with
|
||||||
`pytest tests`. Nevertheless, an LLM wrote this: read *Model & limits*
|
`uv run pytest`. Real boards were measured against a UT3513+ micro-ohm
|
||||||
|
meter (see *Measured vs. computed* above). Nevertheless, an LLM wrote
|
||||||
|
this: read *Model & limits*
|
||||||
critically, treat surprising numbers with the usual engineering
|
critically, treat surprising numbers with the usual engineering
|
||||||
suspicion, and cross-check against a hand estimate before trusting a
|
suspicion, and cross-check against a hand estimate before trusting a
|
||||||
result with hardware. Bug reports are very welcome.
|
result with hardware. Bug reports are very welcome.
|
||||||
|
|||||||
+2
-1
@@ -30,7 +30,8 @@ if ($Mode -eq 'Junction') {
|
|||||||
New-Item -ItemType Junction -Path $dst -Target $src | Out-Null
|
New-Item -ItemType Junction -Path $dst -Target $src | Out-Null
|
||||||
Write-Host "junction created: $dst -> $src"
|
Write-Host "junction created: $dst -> $src"
|
||||||
} else {
|
} else {
|
||||||
$exclude = @('.venv', '.git', 'tests', 'tools', '__pycache__', '.pytest_cache')
|
$exclude = @('.venv', '.git', 'tests', 'tools', '__pycache__', '.pytest_cache',
|
||||||
|
'pyproject.toml', 'uv.lock')
|
||||||
New-Item -ItemType Directory -Force $dst | Out-Null
|
New-Item -ItemType Directory -Force $dst | Out-Null
|
||||||
Get-ChildItem $src -Force | Where-Object { $exclude -notcontains $_.Name } |
|
Get-ChildItem $src -Force | Where-Object { $exclude -notcontains $_.Name } |
|
||||||
ForEach-Object { Copy-Item $_.FullName -Destination $dst -Recurse -Force }
|
ForEach-Object { Copy-Item $_.FullName -Destination $dst -Recurse -Force }
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ def fig_power(result, stack, e1, e2, problem):
|
|||||||
def fig_error(message: str):
|
def fig_error(message: str):
|
||||||
fig, ax = plt.subplots(figsize=(9, 4.5), layout="constrained")
|
fig, ax = plt.subplots(figsize=(9, 4.5), layout="constrained")
|
||||||
ax.axis("off")
|
ax.axis("off")
|
||||||
ax.set_title("Fill Resistance — ERROR", color="#b02a2a",
|
ax.set_title("Fill Resistance - ERROR", color="#b02a2a",
|
||||||
fontsize=14, fontweight="bold", loc="left")
|
fontsize=14, fontweight="bold", loc="left")
|
||||||
wrapped = "\n".join(
|
wrapped = "\n".join(
|
||||||
textwrap.fill(line, width=90) for line in message.splitlines()
|
textwrap.fill(line, width=90) for line in message.splitlines()
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Development environment only (uv sync / uv run). The KiCad plugin
|
||||||
|
# manager builds the runtime venv itself from requirements.txt — keep
|
||||||
|
# the dependency list there in sync with [project.dependencies].
|
||||||
|
[project]
|
||||||
|
name = "fill-resistance"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "DC/AC resistance of copper zone fills and traces between two contacts (KiCad 10 plugin)"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"kicad-python>=0.7.0",
|
||||||
|
"numpy",
|
||||||
|
"scipy",
|
||||||
|
"pyamg",
|
||||||
|
"matplotlib",
|
||||||
|
"PySide6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
package = false
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
+1
-1
@@ -18,7 +18,7 @@ from pathlib import Path
|
|||||||
ROOT = Path(__file__).resolve().parent.parent
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
COPY_EXCLUDE = {".venv", ".git", "tests", "tools", "dist", "resources",
|
COPY_EXCLUDE = {".venv", ".git", "tests", "tools", "dist", "resources",
|
||||||
"__pycache__", ".pytest_cache", "conftest.py", "deploy.ps1",
|
"__pycache__", ".pytest_cache", "conftest.py", "deploy.ps1",
|
||||||
".gitignore", "metadata.json"}
|
".gitignore", "metadata.json", "pyproject.toml", "uv.lock"}
|
||||||
|
|
||||||
|
|
||||||
def plugins_dir(kicad_version: str) -> Path:
|
def plugins_dir(kicad_version: str) -> Path:
|
||||||
|
|||||||
Reference in New Issue
Block a user