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:
@@ -71,6 +71,8 @@ def write_sch_page(
|
||||
title: str | None = None,
|
||||
sheet_origin_mm: tuple[float, float] = (25.4, 25.4),
|
||||
project_relations: ProjectRelations | None = None,
|
||||
sheet_path: str = "/",
|
||||
page_num: int = 1,
|
||||
) -> str:
|
||||
"""Render a single SCH_PAGE Document as kicad_sch text.
|
||||
|
||||
@@ -82,6 +84,16 @@ def write_sch_page(
|
||||
hosting that PART; the body of the first matching SYMBOL is rendered via
|
||||
:func:`sym_writer.write_lib_symbol`. If a partId can't be resolved, we
|
||||
still emit the placement (KiCad shows a red ``?``).
|
||||
|
||||
``sheet_path`` and ``page_num`` describe this page's place in a
|
||||
hierarchical project. For a standalone page, the defaults emit
|
||||
``(sheet_instances (path "/" (page "1")))`` which KiCad reads as a
|
||||
single-sheet project. When this page is included as a child of a root
|
||||
schematic, pass ``sheet_path="/<assigned_child_uuid>"`` (the uuid the
|
||||
root assigned to its ``(sheet ...)`` block for this child) and the
|
||||
page index in the hierarchy (root=1, children=2..N). Without this the
|
||||
KiCad hierarchy can't bind the child file to its root entry, and ERC
|
||||
treats the child as a disconnected island.
|
||||
"""
|
||||
if doc.doc_type != "SCH_PAGE":
|
||||
raise ValueError(f"expected SCH_PAGE doc, got {doc.doc_type!r}")
|
||||
@@ -129,11 +141,19 @@ def write_sch_page(
|
||||
net = wire_net_cache[wid]
|
||||
if not net:
|
||||
continue
|
||||
# global_label, not local label: EPRO2 nets are project-wide
|
||||
# (a "GND" net spans every page in the schematic and physically
|
||||
# connects to GND wires on neighbour PCBs). KiCad's local (label)
|
||||
# is sheet-scoped and triggers `label_dangling` whenever a name
|
||||
# only appears on one sheet — exactly the case we hit on every
|
||||
# cross-sheet net before. (global_label) is project-scoped and
|
||||
# gets resolved by hierarchical ERC on the root sheet.
|
||||
elements.append([
|
||||
Sym("label"), str(net),
|
||||
Sym("global_label"), str(net),
|
||||
[Sym("shape"), Sym("passive")],
|
||||
[Sym("at"), x1, y1, 0],
|
||||
[Sym("effects"), [Sym("font"), [Sym("size"), 1.27, 1.27]],
|
||||
[Sym("justify"), Sym("left"), Sym("bottom")]],
|
||||
[Sym("justify"), Sym("left")]],
|
||||
[Sym("uuid"), _new_uuid()],
|
||||
])
|
||||
stats.labels += 1
|
||||
@@ -178,6 +198,24 @@ def write_sch_page(
|
||||
elements.append(sym_block)
|
||||
stats.symbol_placements += 1
|
||||
|
||||
# Power-port instances: EPRO2 expresses things like VCC / GND / VBUS
|
||||
# as a generic "5-Voltage" symbol whose net name is carried by the
|
||||
# placement's `Global Net Name` ATTR (not by the underlying PART).
|
||||
# Without a global_label at the pin tip, every such placement
|
||||
# shows up as pin_not_connected even though it should anchor the
|
||||
# pin to a global rail.
|
||||
gnn = attrs.get("Global Net Name")
|
||||
if gnn:
|
||||
elements.append([
|
||||
Sym("global_label"), str(gnn),
|
||||
[Sym("shape"), Sym("passive")],
|
||||
[Sym("at"), x, y, 0],
|
||||
[Sym("effects"), [Sym("font"), [Sym("size"), 1.27, 1.27]],
|
||||
[Sym("justify"), Sym("left")]],
|
||||
[Sym("uuid"), _new_uuid()],
|
||||
])
|
||||
stats.labels += 1
|
||||
|
||||
# 3. Text labels from TEXT objects (best-effort — only those with a non-empty value).
|
||||
for oid, obj in doc.objects.items():
|
||||
if obj.get("_type") != "TEXT":
|
||||
@@ -219,7 +257,7 @@ def write_sch_page(
|
||||
lib_symbols,
|
||||
*elements,
|
||||
[Sym("sheet_instances"),
|
||||
[Sym("path"), "/", [Sym("page"), "1"]]],
|
||||
[Sym("path"), sheet_path, [Sym("page"), str(page_num)]]],
|
||||
]
|
||||
|
||||
# Stash stats on the function for tests / CLI to inspect.
|
||||
|
||||
Reference in New Issue
Block a user