tools/epro2/kicad: hierarchical export + global_label + 5-Voltage power ports
Three coupled changes so kicad-cli sch erc runs at the project level (across all sheets of one schematic) instead of single-sheet: 1. (label) → (global_label (shape passive)). EPRO2 nets are project-global by construction (named rails span every page in the SCH and physically wire across PCBs); KiCad's local label is sheet- scoped and triggers `label_dangling` for any name not duplicated on the same page. 2. New root_sch_writer that groups SCH_PAGE docs by their parent SCH (META.schematic), emits one root .kicad_sch per group with one (sheet ...) entry per child, and threads the root-assigned uuid back into each child's (sheet_instances) so KiCad can bind them. --all-sch now defaults to this; --flat falls back to one-file-per-page. 3. EPRO2's "5-Voltage" placeholder COMPONENT (partId pid8a0e77bacb214e, 365 instances on ESP-VoCat) is the editor's power port. The rail name lives in the placement's `Global Net Name` ATTR, not in the PART. We now emit a (global_label "<rail>") at the placement coords whenever that attr is set (101/365 of them on ESP-VoCat — the rest are unconfigured drafts). ESP-VoCat 5 hierarchical roots: 2325 → 2265 violations. Modest because 5 of 6 SCHs are single-page (no cross-sheet nets to resolve), and the one 4-page schematic (CoreBoard) shares only a handful of names across sheets — most net names are de-facto sheet-local. The remaining ~190 pin_not_connected are dominated by 0402-style passives whose pin tip lies on a wire's interior, not at an endpoint; KiCad needs an explicit (junction) at those points and we don't yet emit one. Marked as the next follow-up in log.md. 47 → 52 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
tools/epro2/kicad/root_sch_writer.py
Normal file
125
tools/epro2/kicad/root_sch_writer.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""Generate a root ``.kicad_sch`` that ties multiple SCH_PAGE files into one
|
||||
hierarchical project.
|
||||
|
||||
EPRO2 multi-page schematics live as N standalone ``SCH_PAGE`` docs all
|
||||
referencing the same ``SCH`` doc through ``META.schematic``. When we exported
|
||||
each page as its own ``.kicad_sch`` and ran ``kicad-cli sch erc`` against it
|
||||
in isolation, every cross-sheet net showed up as ``label_dangling`` /
|
||||
``pin_not_connected`` because single-sheet ERC can't see the matching
|
||||
endpoint on a sibling page.
|
||||
|
||||
Hierarchical mode fixes that:
|
||||
- Root .kicad_sch carries one ``(sheet ...)`` block per child page,
|
||||
each with a uuid the root assigns and a ``Sheetfile`` pointing at the
|
||||
child filename in the same directory.
|
||||
- Each child .kicad_sch declares its position in the hierarchy with
|
||||
``(sheet_instances (path "/<assigned_uuid>" (page "<n>")))`` so KiCad
|
||||
can bind it to the root.
|
||||
- With both halves wired up, ``kicad-cli sch erc <root>`` walks the whole
|
||||
project, resolving global_labels across pages.
|
||||
|
||||
Usage:
|
||||
Build a list of (filename, page_title, assigned_uuid) tuples — one per
|
||||
child — *before* writing the children, then pass it here to emit the
|
||||
root. The same uuid you stored in this list goes into the child writer
|
||||
via its ``sheet_path="/<uuid>"`` argument.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid as _uuid
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .sexpr import Sym, to_sexpr
|
||||
|
||||
KICAD_SCH_VERSION = 20231120
|
||||
KICAD_GENERATOR = "facere-epro2"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChildSheet:
|
||||
"""One child entry in a root sheet.
|
||||
|
||||
``filename`` is relative to the root .kicad_sch's directory.
|
||||
``sheet_uuid`` is the uuid the root assigns to this child; the child's
|
||||
own (sheet_instances) must echo it back as ``"/<sheet_uuid>"``.
|
||||
"""
|
||||
|
||||
filename: str
|
||||
title: str
|
||||
sheet_uuid: str
|
||||
|
||||
|
||||
def new_sheet_uuid() -> str:
|
||||
return str(_uuid.uuid4())
|
||||
|
||||
|
||||
def _font(size: float = 1.27) -> list:
|
||||
return [Sym("effects"), [Sym("font"), [Sym("size"), size, size]],
|
||||
[Sym("justify"), Sym("left"), Sym("bottom")]]
|
||||
|
||||
|
||||
def _file_font(size: float = 1.27) -> list:
|
||||
return [Sym("effects"), [Sym("font"), [Sym("size"), size, size]],
|
||||
[Sym("justify"), Sym("left"), Sym("top")]]
|
||||
|
||||
|
||||
def write_root_sheet(
|
||||
title: str,
|
||||
children: list[ChildSheet],
|
||||
*,
|
||||
project_name: str = "facere",
|
||||
grid_origin_mm: tuple[float, float] = (38.1, 38.1),
|
||||
box_size_mm: tuple[float, float] = (50.8, 25.4),
|
||||
box_pitch_mm: tuple[float, float] = (63.5, 38.1),
|
||||
columns: int = 3,
|
||||
) -> str:
|
||||
"""Render a root ``.kicad_sch`` listing ``children`` as ``(sheet ...)`` blocks.
|
||||
|
||||
The boxes are laid out on a simple ``columns``-wide grid so they don't
|
||||
overlap on the canvas; visual layout is irrelevant to ERC but KiCad's
|
||||
GUI needs non-zero positions/sizes for the sheet to be visible/editable.
|
||||
"""
|
||||
elements: list = []
|
||||
bw, bh = box_size_mm
|
||||
px, py = box_pitch_mm
|
||||
ox, oy = grid_origin_mm
|
||||
|
||||
for idx, child in enumerate(children):
|
||||
col = idx % columns
|
||||
row = idx // columns
|
||||
x = ox + col * px
|
||||
y = oy + row * py
|
||||
page_num = idx + 2 # root is page 1, children start at 2
|
||||
|
||||
elements.append([
|
||||
Sym("sheet"),
|
||||
[Sym("at"), x, y],
|
||||
[Sym("size"), bw, bh],
|
||||
[Sym("stroke"), [Sym("width"), 0.1524], [Sym("type"), Sym("solid")]],
|
||||
[Sym("fill"), [Sym("color"), 0, 0, 0, 0.0]],
|
||||
[Sym("uuid"), child.sheet_uuid],
|
||||
[Sym("property"), "Sheetname", child.title,
|
||||
[Sym("at"), x, y - 0.5, 0], _font()],
|
||||
[Sym("property"), "Sheetfile", child.filename,
|
||||
[Sym("at"), x, y + bh + 0.5, 0], _file_font()],
|
||||
[Sym("instances"),
|
||||
[Sym("project"), project_name,
|
||||
[Sym("path"), "/", [Sym("page"), str(page_num)]]]],
|
||||
])
|
||||
|
||||
sch: list = [
|
||||
Sym("kicad_sch"),
|
||||
[Sym("version"), KICAD_SCH_VERSION],
|
||||
[Sym("generator"), KICAD_GENERATOR],
|
||||
[Sym("uuid"), new_sheet_uuid()],
|
||||
[Sym("paper"), "A4"],
|
||||
[Sym("title_block"),
|
||||
[Sym("title"), title],
|
||||
[Sym("comment"), 1, f"hierarchical root for {len(children)} child sheet(s)"]],
|
||||
[Sym("lib_symbols")],
|
||||
*elements,
|
||||
[Sym("sheet_instances"),
|
||||
[Sym("path"), "/", [Sym("page"), "1"]]],
|
||||
]
|
||||
return to_sexpr(sch, pretty=True)
|
||||
Reference in New Issue
Block a user