--- name: kicad-bom-edit description: Use when adding or editing BOM/part fields (MPN, LCSC, Manufacturer, tolerance, voltage/current ratings, DNP) on KiCad .kicad_sch symbols by hand or script, or when a kicad-cli BOM export is missing parts. Covers safe property insertion, the "malformed property silently drops a whole sheet" pitfall, and the netlist-vs-BOM count verification that catches it. KiCad 7/8/9/10 S-expression schematics. --- # Editing KiCad schematics for BOM Adding manufacturer part numbers (MPN), LCSC codes, ratings, etc. to a KiCad `.kicad_sch` so the BOM can be ordered/assembled (e.g. JLCPCB). **Prefer the KiCad GUI** (Symbol Fields Table / per-symbol properties) — it can't corrupt the file. Only hand/script-edit when the GUI isn't available, and then follow this exactly. ## How symbols store fields Each placed symbol is `(symbol (lib_id ...) (at ...) ... (instances ...))`. A field is a **property**, a direct child of the symbol: ``` (property "MPN" "FNR4018S3R3MT" (at 139.7 87.63 0) (hide yes) (show_name no) (do_not_autoplace no) (effects (font (size 1.27 1.27))) ) ``` Properties `Reference`, `Value`, `Footprint`, `Datasheet`, `Description` are mandatory; `MPN`/`LCSC`/`Manufacturer`/`V`/`Type`/`Current`/etc. are custom BOM fields. Field **names must be unique** within a symbol. `kicad-cli sch export bom --fields '...'` selects which show up as columns. ## ⚠️ THE PITFALL: a malformed property silently drops the WHOLE sheet New BOM fields must be **siblings** of `Value` (direct children of the symbol) — NOT nested inside another property. If you accidentally place them *inside* the `Value` property, `kicad-cli sch export bom` will **silently omit every component on that sheet** — no warning, exit code 0 — while `kicad-cli sch export netlist` still parses fine and shows nothing wrong. A whole page of parts (regulators, connectors…) can vanish from the BOM you send to the fab. **Root cause seen in practice:** naive regex insertion. Property indentation **varies by file** (some use 2 tabs for `(property`, some 1). A regex like `.*?\n\t\t\t\)` grabs the first 3-tab close — which is the inner `effects`/`font` close, not the property's own close — so the insert lands *inside* `Value`. ## Safe insertion (script) 1. **Match the entire target property block by balanced parens**, keyed on its unique content (e.g. `Value` = "2.2uH" at a known `(at ...)`), from the `(property "Value"` line to *its own* closing paren at the property's indent level. Don't rely on a fixed tab count guessed from another file — detect the file's actual property indentation first. 2. Insert new `(property ...)` blocks **after** that close, at the **same indent level** as `Value` (siblings), with children one level deeper. 3. Keep every block paren-balanced (each `(property` / `(effects` / `(font` has its close). Robust pattern (Python) — replace the whole Value block and append siblings: ```python import re src=open(f).read() # whole L1 Value property: 2-tab property line, 3+tab children, 2-tab close pat=re.compile(r'\t\t\(property "Value" "2\.2uH"\n(?:\t\t\t.*\n)*?\t\t\)\n') m=pat.search(src) def prop(n,v): return (f'\t\t(property "{n}" "{v}"\n\t\t\t(at 139.7 87.63 0)\n\t\t\t(hide yes)\n' f'\t\t\t(show_name no)\n\t\t\t(do_not_autoplace no)\n\t\t\t(effects\n\t\t\t\t(font\n' f'\t\t\t\t\t(size 1.27 1.27)\n\t\t\t\t)\n\t\t\t)\n\t\t)\n') block=m.group(0).replace('"2.2uH"','"3.3uH"') # keep Value block intact add=prop("MPN","FNR4018S3R3MT")+prop("LCSC","C167805")+prop("Manufacturer","cjiang") src=src[:m.start()]+block+add+src[m.end():] open(f,'w').write(src) ``` (Whitespace inside S-expressions is not significant to KiCad — matching indent is only for legibility and to hit the right closing paren. KiCad rewrites formatting on its next save.) ## ✅ ALWAYS verify: netlist component count must equal BOM row count This is the check that catches the silent-drop bug. After any schematic edit: ```bash kicad-cli sch export netlist --format kicadsexpr -o /tmp/n.net project.kicad_sch kicad-cli sch export bom --fields 'Reference,Value,MPN,LCSC' -o /tmp/b.csv project.kicad_sch # count components in each; they MUST match ``` Parse the netlist `(components (comp (ref ...)))` for the ref set, and the BOM CSV first column for its ref set, and print the difference. If the BOM set is smaller, some sheet was dropped — you have a malformed property. Confirm the specific row (`grep '"L1"' /tmp/b.csv`) shows its new fields populated. ## Dual-project-instance sheets A sub-sheet that was once its own standalone project (e.g. `Power.kicad_sch` reused in `re-bba-rb`) has `(instances (project "power" ...) (project "re-bba-rb" ...))` on each symbol. This is normal, but it makes such sheets extra-sensitive to malformed properties in BOM export — always run the count check after editing these. ## Part selection for JLCPCB / LCSC - Fill `MPN`, `Manufacturer`, and the **`LCSC` C-number** (e.g. `C167805`) — the LCSC code is what JLC assembly needs. Don't invent C-numbers; verify each on lcsc.com / jlcpcb.com/parts (existence, stock, **Basic vs Extended** — Extended = small one-time fee). - For power inductors, record and check **Isat** (saturation ≥ peak current) and **Irms**; a value/footprint alone isn't enough. - Match the field-naming already used in the project (this repo uses `V`, `Type`, `Current`, `MPN`, `LCSC`, `Manufacturer`).