Files
rebbarb/.claude/skills/kicad-manufacturing-export/SKILL.md
T
Roflin f2a721cb1f docs: record the two CPL export steps the recipe was missing
Regenerating the production files for the V2.1 eFuse rework surfaced two
steps that neither the skill nor CPL_ROTATIONS.md documented, both caught
only by diffing against the previously-shipped CPL:

- --exclude-dnp does not drop 'in_bom no' parts, so J4 and TP1-TP5 landed in
  the raw export although the shipped CPL excluded them.  Filter to
  designators that have a BOM line; the two sets should end up equal.
- kicad-cli emits signed rotations while the correction step's % 360 only
  normalises corrected parts.  Mixing the two made the diff show 34 phantom
  rotation changes (270.0 vs -90.0 is the same angle), burying the 2 real
  ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 15:20:56 +02:00

156 lines
7.5 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.
---
name: kicad-manufacturing-export
description: Use when regenerating or exporting the BOM CSV, CPL/position CSV, or Gerbers+drill zip for a KiCad PCB project via kicad-cli (e.g. "regenerate the gerbers", "update the BOM and CPL", "re-export for JLCPCB"). Covers the exact kicad-cli invocations and column formats this project's ordering files expect, and the CPL rotation-correction trap (regenerating the position file the naive way silently wipes hand-verified assembly rotation fixes). KiCad 8/9/10, JLCPCB-style deliverables.
---
# Regenerating BOM / CPL / Gerbers via kicad-cli
For the `re-bba-rb` board specifically, deliverables live in
`hardware/re-bba-rb/`: `re-bba-rb-bom.csv`, `re-bba-rb-cpl.csv`,
`gerbers/rebbarb.zip`. Adjust paths/output names for other boards, but the
gotchas below (especially the CPL rotation trap) apply generally.
## 1. Gerbers + drill + zip
```bash
cd hardware/re-bba-rb
kicad-cli pcb export gerbers --board-plot-params -o gerbers/ re-bba-rb.kicad_pcb
kicad-cli pcb export drill --format excellon --excellon-separate-th \
--excellon-units mm -o gerbers/ re-bba-rb.kicad_pcb
```
`--board-plot-params` reuses whatever layer/plot settings are already stored
in the board file — don't hand-pick layers, this reproduces exactly what was
there before. `--excellon-separate-th` splits plated/non-plated holes into
`*-PTH.drl` / `*-NPTH.drl`, matching the existing file-naming convention.
Then re-zip (excluding the previous zip itself, so it's not nested inside
its own replacement):
```python
import zipfile, os
folder = 'gerbers'
files = sorted(f for f in os.listdir(folder) if f != 'rebbarb.zip')
with zipfile.ZipFile(os.path.join(folder, 'rebbarb.zip'), 'w', zipfile.ZIP_DEFLATED) as z:
for f in files:
z.write(os.path.join(folder, f), arcname=f)
```
kicad-cli's gerber export also produces a `*-job.gbrjob` metadata file
alongside the layer files — include it in the zip, it's harmless and some
fabs use it for auto-configuration.
## 2. BOM
```bash
kicad-cli sch export bom \
--fields "ITEM_NUMBER,Reference,Value,Footprint,MPN,LCSC,QUANTITY,DNP,Manufacturer" \
--labels "Item,Designator,Value,Footprint,MPN,LCSC,Qty,DNP,Manufacturer" \
--group-by "Value,Footprint,MPN,LCSC,Manufacturer" \
--sort-field "Reference" \
--ref-range-delimiter "" \
-o re-bba-rb-bom.csv \
re-bba-rb.kicad_sch
```
`--ref-range-delimiter ""` disables range compression (`C18-C20` becomes
`C18,C19,C20`) — this project's existing BOM convention is flat comma lists,
not ranges; drop this flag if the target format wants ranges instead.
`--group-by` on those 5 fields means any two components sharing value +
footprint + MPN + LCSC + manufacturer collapse into one row (with a combined
`Qty`) — this is how e.g. adding a new part that happens to match an
*already-used* part (same value/footprint/MPN) merges into an existing BOM
line instead of creating a new one, which is usually what you want (no new
line item added to the order).
Reference-only / non-purchasable footprints (gold-finger connectors, mouse-bite
NPTH-hole footprints, etc.) are excluded automatically via `in_bom no` on the
schematic symbol (or `exclude_from_bom` in the footprint's `attr`) — don't
try to filter them out in the export command itself.
**Verify after export**: diff the new BOM against the old one and sanity-check
that only expected rows changed — an unexpectedly *shorter* BOM (fewer
component rows/refs than before) is the signature of the
[kicad-bom-edit](../kicad-bom-edit/SKILL.md) skill's "malformed property
silently drops a whole sheet" bug, not a real BOM change.
## 3. CPL / position file — the rotation trap
```bash
kicad-cli pcb export pos --format csv --units mm --side both --exclude-dnp \
-o /tmp/pos_raw.csv re-bba-rb.kicad_pcb
```
kicad-cli's own CSV columns (`Ref,Val,Package,PosX,PosY,Rot,Side`) don't match
JLCPCB's expected format (`Designator,Mid X,Mid Y,Layer,Rotation`, with
`Layer` capitalized `Top`/`Bottom`, not lowercase `top`/`bottom`). Transform:
```python
import csv
with open('/tmp/pos_raw.csv', newline='') as f:
rows = list(csv.DictReader(f))
with open('re-bba-rb-cpl.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['Designator', 'Mid X', 'Mid Y', 'Layer', 'Rotation'])
for r in rows:
layer = 'Top' if r['Side'].strip().lower() == 'top' else 'Bottom'
writer.writerow([r['Ref'], f"{float(r['PosX']):.6f}",
f"{float(r['PosY']):.6f}", layer,
f"{float(r['Rot']):.6f}"])
```
**Two filtering/formatting steps the raw export does NOT do** (both were
silently missing once and had to be rediscovered by diffing against the
previously-shipped file — do them every time):
1. **Drop rows with no BOM line.** `--exclude-dnp` only filters `dnp yes`.
Parts marked `in_bom no` but `in_pos_files yes` and `dnp no` — on this board
J4 (unpopulated debug header) and TP1–TP5 (bare test pads) — still land in
the raw export. The assembler has no part to place for them. Filter the CPL
to designators that appear in the BOM; afterwards the two sets should be
exactly equal, which is a good assertion to make:
```python
placed = {x.strip() for r in csv.DictReader(open('re-bba-rb-bom.csv'))
for x in r['Designator'].split(',') if x.strip()}
rows = [r for r in rows if r['Designator'] in placed]
```
2. **Normalise every rotation to [0, 360).** `kicad-cli` emits signed angles
(`-90`), while the correction step's `% 360` leaves corrected parts in
0–360. Mixing the two makes a diff against the previous CPL show ~34
phantom changes (`270.0` vs `-90.0` is the same angle) and buries the real
ones. Apply `% 360` to **all** rows, not just corrected ones.
**Then — before treating this as done — reapply every correction in
[`hardware/re-bba-rb/CPL_ROTATIONS.md`](../../../hardware/re-bba-rb/CPL_ROTATIONS.md).**
KiCad's raw per-footprint rotation frequently does not match what the
assembler expects (arbitrary per-footprint authoring reference vs. the
assembler's actual placement convention), and this has already been
hand-verified part-by-part against JLCPCB's placement-preview render for
this board. Regenerating the position file "the normal way" silently
overwrites those fixes back to (wrong) raw values — there is no automated
check that catches this, so it's easy to quietly ship a regression. Read
that file's sign-convention note before applying anything (short version:
"N degrees clockwise" feedback means *subtract* N in the CPL's Y-up frame,
not add it).
Do **not** reach for a generic community rotation-correction table (e.g.
`cpl_rotations_db.csv` from JLCKicadTools) as a shortcut — one was tried on
this board and found wrong for at least one package family (claimed
`LQFP-: 270°`; this board's actual correct value is 0°, unmodified). That
table is calibrated against a specific plugin's own raw-rotation baseline,
which is not the same as `kicad-cli`'s raw CSV baseline (different Y-axis
handling) — the numbers don't transfer.
## Quick full-refresh recipe
1. Gerbers/drill/zip: regenerate + re-zip (section 1) — safe to always redo,
no hidden state to lose.
2. BOM: regenerate (section 2) — safe to always redo. Diff-check the row
count.
3. CPL: regenerate raw (section 3), **then reapply CPL_ROTATIONS.md** before
calling it current.
4. Run ERC (`kicad-cli sch erc --severity-all`) and DRC
(`kicad-cli pcb drc --severity-all`) after any schematic/PCB change that
prompted the refresh — not the export step itself, but worth confirming
nothing regressed if this was bundled with other edits.