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:
@@ -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
|
||||
|
||||
|
@@ -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))
|
||||
)
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user