feat: multi-sheet net connectivity + sexp-based parsing reliability

Adds robust multi-sheet (hierarchical) net connectivity for KiCad
schematics and switches the wire/label parsing to a direct sexpdata
pipeline that bypasses kicad-skip's collection iteration, which was
silently dropping wires, labels, and symbol instances on real-world
schematics.

python/commands/wire_connectivity.py
  - New sexpdata helpers: _load_sexp, _parse_wires_sexp,
    _parse_labels_sexp, _parse_symbol_instances_sexp,
    _parse_hierarchical_labels_sexp, _discover_sub_sheets.
  - _build_adjacency now detects T-junctions (endpoint landing on
    another wire's interior segment) so adjacency captures connections
    KiCad doesn't represent as separate wire segments.
  - _find_connected_wires gains an interior-segment fallback so labels
    placed mid-wire still seed BFS correctly.
  - _parse_virtual_connections gathers label / global_label /
    hierarchical_label and power-symbol pin positions, with a
    kicad-skip fallback for unit tests that mock the schematic.
  - _find_pins_on_net rebuilds pin positions from sexpdata symbol
    instances (with mirror_x/mirror_y/rotation handling) and uses a
    plus/minus 1 IU tolerance for floating-point edge cases.
  - get_connections_for_net walks the top sheet plus every recursively
    discovered sub-sheet, deduping pins across sheets.

python/commands/pin_locator.py
  - lib_id matching now falls back to a bare-name + unit-suffix match
    so instances like "stat-tis-custom:BAT_18650" resolve to
    lib_symbols entries like "BAT_18650_3".
  - Pin position math now y-negates lib_symbols coords, applies
    mirror_x/mirror_y in local coords before rotation, and propagates
    the same transform into get_pin_orientation so downstream callers
    get a correct outward angle for mirrored symbols.

python/commands/connection_schematic.py
  - generate_netlist now collects nets from both label and
    global_label and routes them through get_connections_for_net so
    netlists reflect cross-sheet connectivity instead of single-sheet
    label-only nets.

python/kicad_interface.py
  - list_schematic_nets aggregates net names across the top sheet and
    all sub-sheets via the sexp helpers, then resolves connections
    using get_connections_for_net.
  - get_net_connections delegates to get_connections_for_net for
    consistent multi-sheet results.
This commit is contained in:
William Viana
2026-04-19 23:42:15 -07:00
parent 5b7434fdef
commit 046f33d876
4 changed files with 599 additions and 82 deletions

View File

@@ -420,7 +420,9 @@ class ConnectionManager:
}
"""
try:
netlist = {"nets": [], "components": []}
from commands.wire_connectivity import get_connections_for_net
netlist: Dict[str, Any] = {"nets": [], "components": []}
# Gather all components
if hasattr(schematic, "symbol"):
@@ -438,20 +440,19 @@ class ConnectionManager:
}
netlist["components"].append(component_info)
# Gather all nets from labels
if hasattr(schematic, "label"):
net_names = set()
for label in schematic.label:
if hasattr(label, "value"):
net_names.add(label.value)
# Gather all nets from labels and global labels
net_names: set = set()
for attr_name in ("label", "global_label"):
if hasattr(schematic, attr_name):
for label in getattr(schematic, attr_name):
if hasattr(label, "value"):
net_names.add(label.value)
# For each net, get connections
for net_name in net_names:
connections = ConnectionManager.get_net_connections(
schematic, net_name, schematic_path
)
if connections:
netlist["nets"].append({"name": net_name, "connections": connections})
sch_path_str = str(schematic_path) if schematic_path else ""
for net_name in net_names:
connections = get_connections_for_net(schematic, sch_path_str, net_name)
if connections:
netlist["nets"].append({"name": net_name, "connections": connections})
logger.info(
f"Generated netlist with {len(netlist['nets'])} nets and {len(netlist['components'])} components"