Skip to content

Commit b3505e0

Browse files
jpwonghuangjianpengclaudeaklofas
authored
feat: support top_level_sheets from .kicad_pro (Altium flat multi-page import) (#19)
* feat: support top_level_sheets from .kicad_pro for Altium-imported projects When importing Altium Designer projects into KiCad, the importer registers all schematic pages as top_level_sheets in .kicad_pro instead of creating hierarchical (sheet ...) references. analyze_schematic.py only discovered sub-sheets via (sheet ...) nodes, causing all non-root pages to be silently ignored — typically losing 90%+ of components. This change adds top_level_sheets detection after parse_all_sheets() returns. When the root schematic has no sub-sheets but .kicad_pro lists multiple top_level_sheets, each additional page is parsed and merged into the result. Tested on a 25-page, 3100+ component RK3588 design imported from Altium. Before: 206 components (one page). After: 3162 components (all 25 pages). Closes #18 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: refresh power_symbols after top_level_sheets merge * fix: address Copilot review on top_level_sheets merge - Trigger flat-merge from find_first(root_tree, "sheet") is None instead of `not no_hierarchy`. The redirect path calls analyze_schematic(root, no_hierarchy=True), which previously gated the merge off — analyzing any peer page of an Altium-flat project dropped to root-only content. The new check also avoids triggering on hierarchical projects whose (sheet ...) refs point to missing files. - Store resolved abs paths in extra_sheets and sheets_parsed (matching parse_all_sheets convention). Dedupe by resolved path so duplicate top_level_sheets entries via different relative paths don't double-merge. Validated on glitcher-flattened fixture (root .kicad_sch with zero sheet refs + .kicad_pro top_level_sheets): both root analysis and peer-page analysis now yield 98 components / 44 power symbols, matching the hierarchical baseline. Copilot's third point (extra peers with their own (sheet ...) children not recursively merged) is real but unrelated to the Altium-flat target; deferred as a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: huangjianpeng <huangjianpeng@cvte.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Andrew Klofas <aklofas@gmail.com>
1 parent 2a7dc41 commit b3505e0

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

skills/kicad/scripts/analyze_schematic.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8650,6 +8650,86 @@ def analyze_schematic(path: str, project_root: str | None = None,
86508650

86518651
parsed = parse_all_sheets(path, root_tree=root_tree)
86528652

8653+
# --- top_level_sheets support (Altium flat multi-page imports) ---
8654+
# KiCad's Altium importer registers all pages as top_level_sheets in
8655+
# .kicad_pro instead of creating (sheet ...) hierarchy references.
8656+
# Trigger only when the root has zero (sheet ...) nodes — checking
8657+
# len(sheets_parsed) alone is fragile because parse_all_sheets silently
8658+
# drops (sheet ...) refs whose files are missing, which would otherwise
8659+
# cause us to mix in unrelated top_level_sheets. Runs regardless of
8660+
# no_hierarchy so the sub-sheet redirect path still picks up peers.
8661+
if (len(parsed.get("sheets_parsed", [])) <= 1
8662+
and find_first(root_tree, "sheet") is None):
8663+
pro = load_kicad_pro(path)
8664+
if pro:
8665+
tls = pro.get("schematic", {}).get("top_level_sheets", [])
8666+
if len(tls) > 1:
8667+
root_abs = str(Path(path).resolve())
8668+
extra_sheets = []
8669+
seen = {root_abs}
8670+
for entry in tls:
8671+
fn = entry.get("filename", "")
8672+
if not fn:
8673+
continue
8674+
sheet_path = os.path.join(os.path.dirname(path), fn)
8675+
if not os.path.isfile(sheet_path):
8676+
continue
8677+
abs_path = str(Path(sheet_path).resolve())
8678+
if abs_path in seen:
8679+
continue
8680+
seen.add(abs_path)
8681+
extra_sheets.append(abs_path)
8682+
8683+
if extra_sheets:
8684+
print(f"Note: discovered {len(extra_sheets)} additional "
8685+
f"top-level sheets from .kicad_pro",
8686+
file=sys.stderr)
8687+
sym_inst = parsed.get("root_symbol_instances", {})
8688+
base_idx = len(parsed["sheets_parsed"])
8689+
for sheet_path in extra_sheets:
8690+
try:
8691+
(_, comps, wires, labels, junctions,
8692+
no_connects, _, lib_syms, text_annot,
8693+
bus_elems, title_blk) = \
8694+
parse_single_sheet(
8695+
sheet_path,
8696+
symbol_instances=sym_inst)
8697+
except Exception as exc:
8698+
print(f"Warning: failed to parse "
8699+
f"{os.path.basename(sheet_path)}: {exc}",
8700+
file=sys.stderr)
8701+
continue
8702+
sheet_idx = base_idx
8703+
base_idx += 1
8704+
for c in comps:
8705+
c["_sheet"] = sheet_idx
8706+
for w in wires:
8707+
w["_sheet"] = sheet_idx
8708+
for lb in labels:
8709+
lb["_sheet"] = sheet_idx
8710+
for j in junctions:
8711+
j["_sheet"] = sheet_idx
8712+
for nc in no_connects:
8713+
nc["_sheet"] = sheet_idx
8714+
parsed["components"].extend(comps)
8715+
parsed["wires"].extend(wires)
8716+
parsed["labels"].extend(labels)
8717+
parsed["junctions"].extend(junctions)
8718+
parsed["no_connects"].extend(no_connects)
8719+
parsed["lib_symbols"].update(lib_syms)
8720+
parsed["text_annotations"].extend(text_annot)
8721+
for bk, bv in bus_elems.items():
8722+
if isinstance(bv, list):
8723+
parsed["bus_elements"].setdefault(bk, []).extend(bv)
8724+
elif isinstance(bv, dict):
8725+
parsed["bus_elements"].setdefault(bk, {}).update(bv)
8726+
parsed["sheets_parsed"].append(sheet_path)
8727+
# Power symbols were extracted from root-sheet components
8728+
# only; refresh from the merged list so peer-sheet ones are
8729+
# included.
8730+
parsed["power_symbols"] = extract_power_symbols(
8731+
parsed["components"])
8732+
86538733
all_components = parsed["components"]
86548734
all_wires = parsed["wires"]
86558735
all_labels = parsed["labels"]

0 commit comments

Comments
 (0)