Add generator for single-row castellated edge footprints

New scripts/generate_castellated_edge.py produces a reusable footprint
(round PTH pads + an embedded Edge.Cuts line through their centers) for
mounting a module flush onto another board's edge, ESP32-WROOM style --
distinct from the QFP/SSOP breakout-adapter footprints already in
footprints/other/castellated_breakouts.pretty/. Default pad size (1.0mm
dia / 0.5mm drill) matches the vias used in reference/kicad-castellated-breakouts.

Generated CASTELLATED_EDGE_1x20_P1.27MM (20 pins, 1.27mm pitch) into
footprints/other/castellated_edge.pretty/, registered as
MIKILAB_castellated_edge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5pjBJQ7tf9pwsSs8rG2sm
This commit is contained in:
2026-09-08 00:07:33 +02:00
co-authored by Claude Sonnet 5
parent 25b09cf48a
commit 804e2a2cb8
5 changed files with 149 additions and 0 deletions
+1
View File
@@ -659,3 +659,4 @@ footprint,/var/folders/cv/lwgyj_k14gx2rc3t2rlt29s80000gn/T/snapeda_gdyhq56d/BGA5
footprint,/tmp/kcb_extract/kicad-castellated-breakouts-master/kicad-castellated-breakouts.pretty/QFP48-1.27MM-CASTELLATED.kicad_mod,footprints/other/castellated_breakouts.pretty/QFP48-1.27MM-CASTELLATED.kicad_mod,NEW,3337c229df872fa541e5c96f2c5525fc53600e77eec580ab93f020d41ab44a14,category=other
footprint,/tmp/kcb_extract/kicad-castellated-breakouts-master/kicad-castellated-breakouts.pretty/QFP64-1.27MM-CASTELLATED.kicad_mod,footprints/other/castellated_breakouts.pretty/QFP64-1.27MM-CASTELLATED.kicad_mod,NEW,de3f682f79645ee434d0cbe850bc687bbd65e3cecde3a0c88fe91b84432d8021,category=other
footprint,/tmp/kcb_extract/kicad-castellated-breakouts-master/kicad-castellated-breakouts.pretty/SSOP8-1.27MM-CASTELLATED.kicad_mod,footprints/other/castellated_breakouts.pretty/SSOP8-1.27MM-CASTELLATED.kicad_mod,NEW,3d3beb23324b47f2d46783f537b873f02658c5bd190096ec715119bfe3eb6a76,category=other
footprint,,footprints/other/castellated_edge.pretty/CASTELLATED_EDGE_1x20_P1.27MM.kicad_mod,NEW,5197e9b46b7a8814700067537cb67f65245ce42d7576c7c5a37269877808acc1,category=other;generated by scripts/generate_castellated_edge.py --pins 20 --pitch 1.27
1 type source destination status hash notes
659 footprint /tmp/kcb_extract/kicad-castellated-breakouts-master/kicad-castellated-breakouts.pretty/QFP48-1.27MM-CASTELLATED.kicad_mod footprints/other/castellated_breakouts.pretty/QFP48-1.27MM-CASTELLATED.kicad_mod NEW 3337c229df872fa541e5c96f2c5525fc53600e77eec580ab93f020d41ab44a14 category=other
660 footprint /tmp/kcb_extract/kicad-castellated-breakouts-master/kicad-castellated-breakouts.pretty/QFP64-1.27MM-CASTELLATED.kicad_mod footprints/other/castellated_breakouts.pretty/QFP64-1.27MM-CASTELLATED.kicad_mod NEW de3f682f79645ee434d0cbe850bc687bbd65e3cecde3a0c88fe91b84432d8021 category=other
661 footprint /tmp/kcb_extract/kicad-castellated-breakouts-master/kicad-castellated-breakouts.pretty/SSOP8-1.27MM-CASTELLATED.kicad_mod footprints/other/castellated_breakouts.pretty/SSOP8-1.27MM-CASTELLATED.kicad_mod NEW 3d3beb23324b47f2d46783f537b873f02658c5bd190096ec715119bfe3eb6a76 category=other
662 footprint footprints/other/castellated_edge.pretty/CASTELLATED_EDGE_1x20_P1.27MM.kicad_mod NEW 5197e9b46b7a8814700067537cb67f65245ce42d7576c7c5a37269877808acc1 category=other;generated by scripts/generate_castellated_edge.py --pins 20 --pitch 1.27
@@ -0,0 +1,32 @@
(footprint CASTELLATED_EDGE_1x20_P1.27MM (layer F.Cu) (tedit 00000000)
(descr "Single row of castellated edge pads, 20 pins, 1.27mm pitch. Align this row with the board outline (do not tent) so the mill/router cuts each pad in half, exposing plated copper on the board edge.")
(tags "castellated edge module")
(attr through_hole)
(fp_text reference REF** (at 0 -14.065) (layer F.SilkS)
(effects (font (size 1 1) (thickness 0.15)))
)
(fp_text value CASTELLATED_EDGE_1x20_P1.27MM (at 0 14.065) (layer F.Fab)
(effects (font (size 1 1) (thickness 0.15)))
)
(pad 1 thru_hole circle (at -12.065 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 2 thru_hole circle (at -10.795 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 3 thru_hole circle (at -9.525 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 4 thru_hole circle (at -8.255 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 5 thru_hole circle (at -6.985 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 6 thru_hole circle (at -5.715 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 7 thru_hole circle (at -4.445 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 8 thru_hole circle (at -3.175 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 9 thru_hole circle (at -1.905 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 10 thru_hole circle (at -0.635 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 11 thru_hole circle (at 0.635 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 12 thru_hole circle (at 1.905 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 13 thru_hole circle (at 3.175 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 14 thru_hole circle (at 4.445 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 15 thru_hole circle (at 5.715 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 16 thru_hole circle (at 6.985 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 17 thru_hole circle (at 8.255 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 18 thru_hole circle (at 9.525 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 19 thru_hole circle (at 10.795 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(pad 20 thru_hole circle (at 12.065 0) (size 1 1) (drill 0.5) (layers *.Cu *.Mask))
(fp_line (start -12.565 0) (end 12.565 0) (layer Edge.Cuts) (width 0.1))
)
+1
View File
@@ -65,6 +65,7 @@
(lib (name "MIKILAB_bel_fuse")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/bel_fuse.pretty")(options "")(descr "bel_fuse.pretty"))
(lib (name "MIKILAB_bourns")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/bourns.pretty")(options "")(descr "bourns.pretty"))
(lib (name "MIKILAB_castellated_breakouts")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/castellated_breakouts.pretty")(options "")(descr "castellated_breakouts.pretty"))
(lib (name "MIKILAB_castellated_edge")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/castellated_edge.pretty")(options "")(descr "castellated_edge.pretty"))
(lib (name "MIKILAB_ck")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/ck.pretty")(options "")(descr "ck.pretty"))
(lib (name "MIKILAB_coilcraft")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/coilcraft.pretty")(options "")(descr "coilcraft.pretty"))
(lib (name "MIKILAB_cts")(type "KiCad")(uri "${KIPRJMOD}/footprints/other/cts.pretty")(options "")(descr "cts.pretty"))
+1
View File
@@ -65,6 +65,7 @@
(lib (name "MIKILAB_bel_fuse")(type "KiCad")(uri "${MIKILAB}/footprints/other/bel_fuse.pretty")(options "")(descr "bel_fuse.pretty"))
(lib (name "MIKILAB_bourns")(type "KiCad")(uri "${MIKILAB}/footprints/other/bourns.pretty")(options "")(descr "bourns.pretty"))
(lib (name "MIKILAB_castellated_breakouts")(type "KiCad")(uri "${MIKILAB}/footprints/other/castellated_breakouts.pretty")(options "")(descr "castellated_breakouts.pretty"))
(lib (name "MIKILAB_castellated_edge")(type "KiCad")(uri "${MIKILAB}/footprints/other/castellated_edge.pretty")(options "")(descr "castellated_edge.pretty"))
(lib (name "MIKILAB_ck")(type "KiCad")(uri "${MIKILAB}/footprints/other/ck.pretty")(options "")(descr "ck.pretty"))
(lib (name "MIKILAB_coilcraft")(type "KiCad")(uri "${MIKILAB}/footprints/other/coilcraft.pretty")(options "")(descr "coilcraft.pretty"))
(lib (name "MIKILAB_cts")(type "KiCad")(uri "${MIKILAB}/footprints/other/cts.pretty")(options "")(descr "cts.pretty"))
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""
generate_castellated_edge.py
=============================
Generates a generic, reusable KiCad footprint for a single row of
castellated pads to place on a board's edge (e.g. for a module meant to
be soldered flush onto a motherboard, the same way ESP32-WROOM-style
modules are built) -- as opposed to the QFP/SSOP breakout adapters in
footprints/other/castellated_breakouts.pretty/, which are a different
thing (a fine-pitch-IC-to-hand-solderable-pad adapter).
Technique (same one documented in reference/kicad-castellated-breakouts/
readme.md): a round plated through-hole pad is placed with its center
exactly on the board edge; an Edge.Cuts line embedded in the footprint
runs straight through every pad center. KiCad merges Edge.Cuts graphics
from every footprint into the final board outline, so once this
footprint's row lines up with your board's edge, the mill/router cuts
each hole in half, exposing plated copper on the board edge. Default pad
size (1.0mm dia / 0.5mm drill) matches the vias used throughout that
reference project. Remember to NOT tent these pads when plotting.
Usage:
python3 scripts/generate_castellated_edge.py --pins 20 --pitch 1.27
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import lib_common as lc
ROOT = lc.LIBRARY_ROOT
def build_footprint(pins: int, pitch: float, pad_dia: float, drill: float) -> str:
name = f"CASTELLATED_EDGE_1x{pins}_P{pitch:g}MM"
half_width = (pins - 1) * pitch / 2
margin = pad_dia / 2
lines = [
f"(footprint {name} (layer F.Cu) (tedit 00000000)",
' (descr "Single row of castellated edge pads, '
f'{pins} pins, {pitch:g}mm pitch. Align this row with the board '
'outline (do not tent) so the mill/router cuts each pad in half, '
'exposing plated copper on the board edge.")',
' (tags "castellated edge module")',
" (attr through_hole)",
f" (fp_text reference REF** (at 0 {-half_width - 2}) (layer F.SilkS)",
" (effects (font (size 1 1) (thickness 0.15)))",
" )",
f" (fp_text value {name} (at 0 {half_width + 2}) (layer F.Fab)",
" (effects (font (size 1 1) (thickness 0.15)))",
" )",
]
for i in range(pins):
x = round(-half_width + i * pitch, 4)
lines.append(
f" (pad {i + 1} thru_hole circle (at {x} 0) "
f"(size {pad_dia:g} {pad_dia:g}) (drill {drill:g}) (layers *.Cu *.Mask))"
)
x_start = round(-half_width - margin, 4)
x_end = round(half_width + margin, 4)
lines.append(f" (fp_line (start {x_start} 0) (end {x_end} 0) (layer Edge.Cuts) (width 0.1))")
lines.append(")")
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--pins", type=int, required=True, help="Number of pads in the row")
parser.add_argument("--pitch", type=float, required=True, help="Pad pitch in mm")
parser.add_argument("--pad-dia", type=float, default=1.0, help="Pad diameter in mm (default 1.0, matches reference project)")
parser.add_argument("--drill", type=float, default=0.5, help="Drill diameter in mm (default 0.5, matches reference project)")
parser.add_argument("--category", default="other", choices=lc.CATEGORIES, help="MIKILAB category (default: other)")
args = parser.parse_args()
if args.pins < 2:
parser.error("--pins must be at least 2")
name = f"CASTELLATED_EDGE_1x{args.pins}_P{args.pitch:g}MM"
pretty_dir = ROOT / "footprints" / args.category / "castellated_edge.pretty"
pretty_dir.mkdir(parents=True, exist_ok=True)
dest = pretty_dir / f"{name}.kicad_mod"
if dest.exists():
print(f"error: {dest} already exists", file=sys.stderr)
return 1
dest.write_text(build_footprint(args.pins, args.pitch, args.pad_dia, args.drill), encoding="utf-8")
lc.write_fp_lib_table(ROOT)
lc.write_global_tables(ROOT)
digest = lc.sha256_file(dest)
lc.append_manifest_rows(ROOT, [[
"footprint", "", str(dest.relative_to(ROOT)), "NEW", digest,
f"category={args.category};generated by scripts/generate_castellated_edge.py "
f"--pins {args.pins} --pitch {args.pitch}",
]])
print(f"Wrote {dest.relative_to(ROOT)}")
print(f"Registered as MIKILAB_castellated_edge:{name}")
return 0
if __name__ == "__main__":
raise SystemExit(main())