461 lines
21 KiB
Python
461 lines
21 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the KiCad schematic hierarchy and BOM tables from design_data."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import copy
|
|
import math
|
|
import re
|
|
import sys
|
|
import uuid
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
from design_data import Component, Pin, components # noqa: E402
|
|
|
|
HW = ROOT / "hardware"
|
|
DOCS = ROOT / "docs"
|
|
OUT = ROOT / "outputs"
|
|
HW.mkdir(exist_ok=True)
|
|
DOCS.mkdir(exist_ok=True)
|
|
OUT.mkdir(exist_ok=True)
|
|
|
|
|
|
def q(text: str) -> str:
|
|
return text.replace("\\", "\\\\").replace('"', '\\"')
|
|
|
|
|
|
def safe(text: str) -> str:
|
|
value = re.sub(r"[^A-Za-z0-9_]+", "_", text)
|
|
return value.strip("_") or "PIN"
|
|
|
|
|
|
def category(comp: Component) -> str:
|
|
nets = {pin.net or "" for pin in comp.pins}
|
|
if comp.ref == "U1" or any(n.startswith("REF_RES") for n in nets):
|
|
return "fpga"
|
|
if comp.ref in {"J2", "J5", "D1", "FL1", "U21", "U23", "U24", "U27"} or any(
|
|
n.startswith(("RF_", "MAX_", "ANT_", "TCXO", "OSC40")) for n in nets):
|
|
return "rf_clock"
|
|
if comp.ref == "U22" or any(n.startswith("IMU_") for n in nets):
|
|
return "imu"
|
|
if comp.ref in {"U2", "U3", "U25"} or any(
|
|
n.startswith(("FTDI_", "HARD_", "RISCV_", "UART")) for n in nets):
|
|
return "usb_debug"
|
|
if comp.ref == "U26" or comp.ref.startswith(("JPMOD", "JH", "JHV")) or any(
|
|
n.startswith(("QSPI_", "EXP_", "PMOD")) for n in nets):
|
|
return "configuration_expansion"
|
|
return "power_usb"
|
|
|
|
|
|
SHEETS = ["power_usb", "rf_clock", "imu", "usb_debug", "configuration_expansion", "fpga"]
|
|
|
|
|
|
def chunks(comp: Component) -> list[list[Pin]]:
|
|
if comp.ref == "U1":
|
|
# Fourteen units keeps every unit below 40 balls and the complete 529-ball
|
|
# package readable without losing the common U1 reference.
|
|
size = math.ceil(len(comp.pins) / 14)
|
|
return [comp.pins[i:i + size] for i in range(0, len(comp.pins), size)]
|
|
return [comp.pins]
|
|
|
|
|
|
def geometry(pins: list[Pin]) -> tuple[int, int, dict[str, tuple[int, int, str]]]:
|
|
left = pins[::2]
|
|
right = pins[1::2]
|
|
rows = max(len(left), len(right), 2)
|
|
half_h = max(150, (rows - 1) * 50 + 100)
|
|
endpoints: dict[str, tuple[int, int, str]] = {}
|
|
start_y = (len(left) - 1) * 50
|
|
for idx, pin in enumerate(left):
|
|
endpoints[pin.number] = (-700, start_y - idx * 100, "R")
|
|
start_y = (len(right) - 1) * 50
|
|
for idx, pin in enumerate(right):
|
|
endpoints[pin.number] = (700, start_y - idx * 100, "L")
|
|
return 500, half_h, endpoints
|
|
|
|
|
|
def symbol_library() -> str:
|
|
lines = ["EESchema-LIBRARY Version 2.4", "#encoding utf-8"]
|
|
etypes = {
|
|
"input": "I", "output": "O", "bidirectional": "B", "power_in": "W",
|
|
"power_out": "w", "open_collector": "C", "passive": "P",
|
|
}
|
|
for comp in components:
|
|
name = f"BOARD_{safe(comp.ref)}"
|
|
units = chunks(comp)
|
|
lines += ["#", f"# {name}", "#",
|
|
f"DEF {name} {comp.ref[0]} 0 30 Y Y {len(units)} L N",
|
|
f'F0 "{q(comp.ref)}" 0 200 50 H V C CNN',
|
|
f'F1 "{q(comp.value)}" 0 100 50 H V C CNN',
|
|
f'F2 "{q(comp.footprint)}" 0 -100 50 H I C CNN',
|
|
f'F3 "" 0 0 50 H I C CNN', "DRAW"]
|
|
for unit, pins in enumerate(units, 1):
|
|
half_w, half_h, endpoints = geometry(pins)
|
|
lines.append(f"S {-half_w} {half_h} {half_w} {-half_h} {unit} 1 10 f")
|
|
for pin in pins:
|
|
x, y, orient = endpoints[pin.number]
|
|
lines.append(
|
|
f"X {safe(pin.name)} {pin.number} {x} {y} 200 {orient} 30 30 {unit} 1 "
|
|
f"{etypes.get(pin.etype, 'P')}"
|
|
)
|
|
lines += ["ENDDRAW", "ENDDEF"]
|
|
lines += ["#", "#End Library"]
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def component_block(comp: Component, unit: int, x: int, y: int, stamp: int) -> list[str]:
|
|
name = f"BOARD_{safe(comp.ref)}"
|
|
f0y = y - 100
|
|
return [
|
|
"$Comp", f"L {name} {comp.ref}", f"U {unit} 1 {stamp:08X}", f"P {x} {y}",
|
|
f'F 0 "{q(comp.ref)}" H {x} {f0y} 50 0000 C CNN',
|
|
f'F 1 "{q(comp.value)}" H {x} {y + 100} 50 0000 C CNN',
|
|
f'F 2 "{q(comp.footprint)}" H {x} {y - 200} 50 0001 C CNN',
|
|
f'F 3 "" H {x} {y} 50 0001 C CNN',
|
|
f'F 4 "{q(comp.mpn)}" H {x} {y} 50 0001 C CNN "MPN"',
|
|
f'F 5 "{q(comp.manufacturer)}" H {x} {y} 50 0001 C CNN "Manufacturer"',
|
|
f"\t{unit} {x} {y}", "\t1 0 0 -1", "$EndComp",
|
|
]
|
|
|
|
|
|
def add_pin_connections(lines: list[str], pins: list[Pin], x: int, y: int) -> None:
|
|
_, _, endpoints = geometry(pins)
|
|
for pin in pins:
|
|
px, py, side = endpoints[pin.number]
|
|
px += x
|
|
py += y
|
|
if pin.net is None:
|
|
lines.append(f"NoConn ~ {px} {py}")
|
|
continue
|
|
wire_end = px - 150 if side == "R" else px + 150
|
|
lines.append(f"Wire Wire Line\n\t{px} {py} {wire_end} {py}")
|
|
orient = 2 if side == "R" else 0
|
|
lines.append(f"Text GLabel {wire_end} {py} {orient} 30 BiDi ~ 0\n{pin.net}")
|
|
|
|
|
|
def sheet_file(sheet: str, comps: list[Component], sheet_index: int) -> str:
|
|
lines = [
|
|
"EESchema Schematic File Version 4", "LIBS:board-cache", "EELAYER 29 0",
|
|
"EELAYER END", "$Descr A0 46811 33110", "encoding utf-8", "Sheet 1 1",
|
|
f'Title "Ti375 GNSS/IMU board - {sheet.replace("_", " ").title()}"',
|
|
'Date "2026-08-15"', 'Rev "A0"', 'Comp "Open hardware design for JLCPCB"',
|
|
'Comment1 "Generated from scripts/design_data.py; do not hand-edit"',
|
|
'Comment2 "No LEDs, no push buttons"', 'Comment3 "Ti375C529C3 / MAX2771 / SCH16T-K01-1"',
|
|
'Comment4 "JLC08161H-3313 eight-layer stack"', "$EndDescr",
|
|
]
|
|
x, y = 1800, 1800
|
|
column_width = 3000
|
|
bottom = 31600
|
|
stamp = 0x65000000 + sheet_index * 0x10000
|
|
for comp in comps:
|
|
for unit, pins in enumerate(chunks(comp), 1):
|
|
_, half_h, _ = geometry(pins)
|
|
height = 2 * half_h + 500
|
|
if y + half_h > bottom:
|
|
x += column_width
|
|
y = 1800
|
|
if x > 45000:
|
|
raise RuntimeError(f"{sheet} overflow at {comp.ref}")
|
|
stamp += 1
|
|
lines += component_block(comp, unit, x, y, stamp)
|
|
add_pin_connections(lines, pins, x, y)
|
|
y += height
|
|
lines += ["$EndSCHEMATC"]
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def root_file() -> str:
|
|
lines = [
|
|
"EESchema Schematic File Version 4", "LIBS:board-cache", "EELAYER 29 0", "EELAYER END",
|
|
"$Descr A4 11693 8268", "encoding utf-8", "Sheet 1 7",
|
|
'Title "Ti375 C529 GNSS/IMU acquisition board"', 'Date "2026-08-15"', 'Rev "A0"',
|
|
'Comp "JLCPCB manufacture/assembly package"',
|
|
'Comment1 "USB-C 5 V primary; protected external 5 V; no back-feed"',
|
|
'Comment2 "GPS L1/L1C direct MAX2771 MIXIN_HI; selectable active-antenna bias"',
|
|
'Comment3 "Every Ti375 programmable GPIO has a documented disposition"',
|
|
'Comment4 "Generated source - see docs/design_audit.md"', "$EndDescr",
|
|
]
|
|
for idx, sheet in enumerate(SHEETS, 1):
|
|
x = 1400 + ((idx - 1) % 2) * 4800
|
|
y = 1000 + ((idx - 1) // 2) * 1900
|
|
lines += [
|
|
"$Sheet", f"S {x} {y} 3600 1200", f"U {0x66000000 + idx:08X}",
|
|
f'F0 "{sheet.replace("_", " ").upper()}" 60', f'F1 "{sheet}.sch" 60', "$EndSheet",
|
|
]
|
|
lines += ["$EndSCHEMATC"]
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def write_boms() -> None:
|
|
fields = ["Reference", "Qty", "Value", "Manufacturer", "MPN", "Footprint",
|
|
"Assembly", "DNP", "Description"]
|
|
grouped: dict[tuple[str, ...], list[str]] = defaultdict(list)
|
|
for comp in components:
|
|
key = (comp.value, comp.manufacturer, comp.mpn, comp.footprint,
|
|
comp.assembly, "yes" if comp.dnp else "no", comp.description)
|
|
grouped[key].append(comp.ref)
|
|
with (OUT / "bom.csv").open("w", newline="") as f:
|
|
w = csv.DictWriter(f, fieldnames=fields)
|
|
w.writeheader()
|
|
for key, refs in sorted(grouped.items(), key=lambda item: item[1][0]):
|
|
value, manufacturer, mpn, footprint, assembly, dnp, desc = key
|
|
w.writerow(dict(zip(fields, [",".join(refs), len(refs), value, manufacturer,
|
|
mpn, footprint, assembly, dnp, desc])))
|
|
|
|
jlc_fields = ["Comment", "Designator", "Footprint", "LCSC Part #", "Manufacturer",
|
|
"Manufacturer Part Number", "Quantity", "Assembly Type", "DNP"]
|
|
with (OUT / "jlcpcb_bom.csv").open("w", newline="") as f:
|
|
w = csv.DictWriter(f, fieldnames=jlc_fields)
|
|
w.writeheader()
|
|
for key, refs in sorted(grouped.items(), key=lambda item: item[1][0]):
|
|
value, manufacturer, mpn, footprint, assembly, dnp, _ = key
|
|
w.writerow({"Comment": value, "Designator": ",".join(refs), "Footprint": footprint,
|
|
"LCSC Part #": "", "Manufacturer": manufacturer,
|
|
"Manufacturer Part Number": mpn, "Quantity": len(refs),
|
|
"Assembly Type": assembly if not dnp == "yes" else "DNP", "DNP": dnp})
|
|
|
|
|
|
def duid(text: str) -> str:
|
|
"""Stable UUIDs keep generated KiCad diffs reproducible."""
|
|
return str(uuid.uuid5(uuid.UUID("830005da-d201-4458-b73e-8946620d7012"), text))
|
|
|
|
|
|
def write_native_schematic() -> None:
|
|
"""Write a flat, native KiCad schematic using kiutils' format model.
|
|
|
|
Flat global-label connectivity is deliberate: it keeps 529 FPGA balls and
|
|
273 physical components machine-auditable without hidden hierarchical pins.
|
|
"""
|
|
try:
|
|
from kiutils.schematic import Schematic
|
|
from kiutils.symbol import Symbol, SymbolLib, SymbolPin
|
|
from kiutils.items.common import (Effects, Fill, Font, PageSettings,
|
|
Position, Property, Stroke, TitleBlock)
|
|
from kiutils.items.schitems import (GlobalLabel, HierarchicalSheetInstance,
|
|
NoConnect, SchematicSymbol, SymbolInstance)
|
|
from kiutils.items.syitems import SyRect
|
|
except ImportError as exc:
|
|
raise SystemExit("Install the pinned generator dependency: python3 -m pip install -r requirements.txt") from exc
|
|
|
|
sch = Schematic.create_new()
|
|
sch.version = "20231120"
|
|
sch.generator = "board_generator"
|
|
sch.uuid = duid("schematic-root")
|
|
sch.paper = PageSettings(paperSize="A0")
|
|
sch.titleBlock = TitleBlock(
|
|
title="Ti375 C529 GPS L1/L1C + SCH16T acquisition board",
|
|
date="2026-08-15", revision="A0", company="JLCPCB manufacturing design",
|
|
comments={1: "Generated from official-package connectivity in scripts/design_data.py",
|
|
2: "USB-C 5 V primary / protected EXT_5V / no source back-feed",
|
|
3: "MAX2771 RF connects only to MIXIN_HI; internal LNAs unused",
|
|
4: "No LEDs and no push buttons"})
|
|
# create_new supplies the root instance; make the path explicit and stable.
|
|
sch.sheetInstances = [HierarchicalSheetInstance(instancePath="/", page="1")]
|
|
|
|
font = Font(width=0.8, height=0.8)
|
|
hidden = Effects(font=font, hide=True)
|
|
shown = Effects(font=font)
|
|
etypes = {
|
|
"input": "input", "output": "output", "bidirectional": "bidirectional",
|
|
"power_in": "power_in", "power_out": "power_out",
|
|
"open_collector": "open_collector", "passive": "passive",
|
|
}
|
|
|
|
def ngeom(pins: list[Pin]):
|
|
left, right = pins[::2], pins[1::2]
|
|
rows = max(len(left), len(right), 2)
|
|
half_h = max(3.81, (rows - 1) * 1.27 + 2.54)
|
|
endpoints = {}
|
|
for side_pins, x, angle in ((left, -17.78, 0), (right, 17.78, 180)):
|
|
start_y = -(len(side_pins) - 1) * 1.27
|
|
for idx, pin in enumerate(side_pins):
|
|
endpoints[pin.number] = (x, start_y + idx * 2.54, angle)
|
|
return 12.7, half_h, endpoints
|
|
|
|
lib_by_ref = {}
|
|
for comp in components:
|
|
symbol_name = f"BOARD_{safe(comp.ref)}"
|
|
parent = Symbol.create_new(f"board:{symbol_name}", comp.ref[0], comp.value, comp.footprint)
|
|
parent.pinNames = True
|
|
parent.pinNamesOffset = 0.508
|
|
parent.properties.extend([
|
|
Property(key="MPN", value=comp.mpn, id=4, effects=hidden),
|
|
Property(key="Manufacturer", value=comp.manufacturer, id=5, effects=hidden),
|
|
Property(key="Description", value=comp.description, id=6, effects=hidden),
|
|
])
|
|
seen_power_out_nets: set[str] = set()
|
|
for unit_idx, pins in enumerate(chunks(comp), 1):
|
|
unit = Symbol()
|
|
unit.libId = f"{symbol_name}_{unit_idx}_1"
|
|
half_w, half_h, endpoints = ngeom(pins)
|
|
unit.graphicItems.append(SyRect(start=Position(-half_w, -half_h),
|
|
end=Position(half_w, half_h),
|
|
stroke=Stroke(width=0.254, type="default"),
|
|
fill=Fill(type="background")))
|
|
for pin in pins:
|
|
x, y, angle = endpoints[pin.number]
|
|
electrical_type = etypes.get(pin.etype, "passive")
|
|
if pin.net == "GND" and electrical_type == "bidirectional":
|
|
# Deliberately strapped bidirectional configuration pins
|
|
# (for example unused TPS6287B I2C) are passive straps in
|
|
# this design, not active bus drivers.
|
|
electrical_type = "passive"
|
|
if electrical_type == "power_out" and pin.net in seen_power_out_nets:
|
|
electrical_type = "passive"
|
|
elif electrical_type == "power_out" and pin.net is not None:
|
|
seen_power_out_nets.add(pin.net)
|
|
unit.pins.append(SymbolPin(
|
|
electricalType=electrical_type, graphicalStyle="line",
|
|
position=Position(x, y, angle), length=5.08,
|
|
name=pin.name, number=pin.number,
|
|
nameEffects=Effects(font=Font(width=0.65, height=0.65)),
|
|
numberEffects=Effects(font=Font(width=0.65, height=0.65))))
|
|
parent.units.append(unit)
|
|
sch.libSymbols.append(parent)
|
|
lib_by_ref[comp.ref] = parent
|
|
|
|
# Pack tallest symbols first within functional groups, using generous label clearance.
|
|
ordered = sorted(components, key=lambda comp: (SHEETS.index(category(comp)),
|
|
-max(len(x) for x in chunks(comp)), comp.ref))
|
|
# All origins and margins are exact multiples of KiCad's 1.27-mm
|
|
# connection grid. Symbol-library Y coordinates are Cartesian (positive
|
|
# up), whereas schematic sheet Y coordinates grow downward.
|
|
x, y = 50.8, 38.1
|
|
column_width, bottom = 127.0, 800.1
|
|
for comp in ordered:
|
|
for unit_idx, pins in enumerate(chunks(comp), 1):
|
|
_, half_h, endpoints = ngeom(pins)
|
|
height = 2 * half_h + 10.16
|
|
if y + half_h > bottom:
|
|
x += column_width
|
|
y = 38.1
|
|
if x > 1150.0:
|
|
raise RuntimeError(f"native A0 sheet overflow at {comp.ref}")
|
|
origin_y = y + half_h
|
|
inst_uuid = duid(f"symbol:{comp.ref}:{unit_idx}")
|
|
inst = SchematicSymbol()
|
|
inst.libId = f"board:BOARD_{safe(comp.ref)}"
|
|
inst.position = Position(x, origin_y, 0)
|
|
inst.unit = unit_idx
|
|
inst.inBom = not comp.dnp
|
|
inst.onBoard = True
|
|
inst.dnp = comp.dnp
|
|
inst.uuid = inst_uuid
|
|
inst.properties = [
|
|
Property(key="Reference", value=comp.ref, id=0,
|
|
position=Position(x, origin_y - half_h - 2.0, 0), effects=shown),
|
|
Property(key="Value", value=comp.value, id=1,
|
|
position=Position(x, origin_y + half_h + 2.0, 0), effects=shown),
|
|
Property(key="Footprint", value=comp.footprint, id=2,
|
|
position=Position(x, origin_y, 0), effects=hidden),
|
|
Property(key="Datasheet", value="", id=3,
|
|
position=Position(x, origin_y, 0), effects=hidden),
|
|
Property(key="MPN", value=comp.mpn, id=4,
|
|
position=Position(x, origin_y, 0), effects=hidden),
|
|
Property(key="Manufacturer", value=comp.manufacturer, id=5,
|
|
position=Position(x, origin_y, 0), effects=hidden),
|
|
]
|
|
for pin in pins:
|
|
inst.pins[pin.number] = duid(f"pin:{comp.ref}:{unit_idx}:{pin.number}")
|
|
px, py, angle = endpoints[pin.number]
|
|
pos = Position(x + px, origin_y - py, angle)
|
|
if pin.net is None:
|
|
sch.noConnects.append(NoConnect(position=pos,
|
|
uuid=duid(f"nc:{comp.ref}:{unit_idx}:{pin.number}")))
|
|
else:
|
|
sch.globalLabels.append(GlobalLabel(
|
|
text=pin.net, shape="bidirectional", position=pos, effects=shown,
|
|
uuid=duid(f"label:{comp.ref}:{unit_idx}:{pin.number}")))
|
|
sch.schematicSymbols.append(inst)
|
|
sch.symbolInstances.append(SymbolInstance(
|
|
path=f"/{inst_uuid}", reference=comp.ref, unit=unit_idx,
|
|
value=comp.value, footprint=comp.footprint))
|
|
y += height
|
|
|
|
# ERC power flags are schematic-only assertions. They are generated only
|
|
# for nets containing power-input pins but no direct power-output pin (for
|
|
# example GND, external connector feeds, and ferrite-isolated subrails).
|
|
power_inputs = {pin.net for comp in components for pin in comp.pins
|
|
if pin.etype == "power_in" and pin.net is not None}
|
|
power_outputs = {pin.net for comp in components for pin in comp.pins
|
|
if pin.etype == "power_out" and pin.net is not None}
|
|
flag_nets = sorted(power_inputs - power_outputs)
|
|
flag_lib = Symbol.create_new("board:PWR_FLAG", "#FLG", "PWR_FLAG")
|
|
flag_lib.inBom = False
|
|
flag_lib.onBoard = False
|
|
flag_lib.isPower = True
|
|
flag_unit = Symbol()
|
|
flag_unit.libId = "PWR_FLAG_1_1"
|
|
flag_unit.pins.append(SymbolPin(
|
|
electricalType="power_out", graphicalStyle="line",
|
|
position=Position(0, 0, 0), length=0, name="~", number="1",
|
|
nameEffects=shown, numberEffects=shown))
|
|
flag_lib.units.append(flag_unit)
|
|
sch.libSymbols.append(flag_lib)
|
|
for idx, net in enumerate(flag_nets, 1):
|
|
fx = 1117.6 if idx <= 24 else 1168.4
|
|
fy = 304.8 + ((idx - 1) % 24) * 12.7
|
|
flag_uuid = duid(f"power-flag:{net}")
|
|
flag = SchematicSymbol()
|
|
flag.libId = "board:PWR_FLAG"
|
|
flag.position = Position(fx, fy, 0)
|
|
flag.unit = 1
|
|
flag.inBom = False
|
|
flag.onBoard = False
|
|
flag.uuid = flag_uuid
|
|
ref = f"#FLG{idx:03d}"
|
|
flag.properties = [
|
|
Property(key="Reference", value=ref, id=0,
|
|
position=Position(fx, fy, 0), effects=hidden),
|
|
Property(key="Value", value="PWR_FLAG", id=1,
|
|
position=Position(fx, fy, 0), effects=hidden),
|
|
Property(key="Footprint", value="", id=2,
|
|
position=Position(fx, fy, 0), effects=hidden),
|
|
Property(key="Datasheet", value="", id=3,
|
|
position=Position(fx, fy, 0), effects=hidden),
|
|
]
|
|
flag.pins["1"] = duid(f"power-flag-pin:{net}")
|
|
sch.schematicSymbols.append(flag)
|
|
sch.globalLabels.append(GlobalLabel(
|
|
text=net, shape="bidirectional", position=Position(fx, fy, 0), effects=shown,
|
|
uuid=duid(f"power-flag-label:{net}")))
|
|
sch.symbolInstances.append(SymbolInstance(
|
|
path=f"/{flag_uuid}", reference=ref, unit=1, value="PWR_FLAG", footprint=""))
|
|
sch.to_file(str(HW / "board.kicad_sch"))
|
|
|
|
external_symbols = copy.deepcopy(sch.libSymbols)
|
|
for symbol in external_symbols:
|
|
symbol.libraryNickname = None
|
|
SymbolLib(version="20231120", generator="board_generator",
|
|
symbols=external_symbols).to_file(str(HW / "board.kicad_sym"))
|
|
(HW / "sym-lib-table").write_text(
|
|
'(sym_lib_table\n (version 7)\n'
|
|
' (lib (name "board")(type "KiCad")(uri "${KIPRJMOD}/board.kicad_sym")(options "")(descr "Generated board symbols"))\n)\n')
|
|
(HW / "fp-lib-table").write_text(
|
|
'(fp_lib_table\n (version 7)\n'
|
|
' (lib (name "board")(type "KiCad")(uri "${KIPRJMOD}/board.pretty")(options "")(descr "Official-land-pattern custom footprints"))\n)\n')
|
|
|
|
|
|
def main() -> None:
|
|
(HW / "board-cache.lib").write_text(symbol_library())
|
|
assignments = {sheet: [] for sheet in SHEETS}
|
|
for comp in components:
|
|
assignments[category(comp)].append(comp)
|
|
for idx, sheet in enumerate(SHEETS, 1):
|
|
(HW / f"{sheet}.sch").write_text(sheet_file(sheet, assignments[sheet], idx))
|
|
(HW / "board.sch").write_text(root_file())
|
|
write_boms()
|
|
write_native_schematic()
|
|
(HW / "board.kicad_pro").write_text(
|
|
'{\n "meta": { "filename": "board.kicad_pro", "version": 1 },\n'
|
|
' "erc": {}, "net_settings": {}, "text_variables": {}\n}\n')
|
|
print(f"generated {len(components)} components on {len(SHEETS)} hierarchical sheets")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|