Fix solver correctness and input-validation issues from code review

- Refuse the uniform contact model when the fills form multiple
  disconnected copper groups that each touch both terminals: the
  prescribed injection split is ill-posed and the grounded system was
  singular, silently returning garbage (e.g. negative gigaohms).
  connected_restrict now reports the component count; a power-balance
  backstop (SolverError) catches any other inconsistent solve.
- Connect via/pad barrels to the nearest fill copper within the pad
  footprint (+1 cell) instead of only the exact center cell, so
  thermal-relief spokes still stitch layers; barrels that reach fill on
  fewer than two layers are warned about. ViaLink gains pad_nm
  (extracted from the padstack, JSON-roundtripped).
- Validate dialog input on OK (layers, current > 0, cell > 0, parseable
  frequency, extra Cu >= 0) with an inline error instead of silently
  substituting defaults; parse_frequency raises on garbage; pipeline
  rejects i_test <= 0; choose_cell_size rejects non-positive overrides.
- Warn when a contact part is dropped by the connectivity restriction;
  floor instead of truncate in cell_of; correct the uniform-model
  summary line; drop an unused variable; refresh plugin.json wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
janik
2026-07-15 14:54:45 +07:00
parent 06c62e04f8
commit e7627352c1
18 changed files with 262 additions and 60 deletions
+100
View File
@@ -0,0 +1,100 @@
"""M1 smoke probe: verify the IPC API surface against a live KiCad.
Run from the dev venv while KiCad is open with the board loaded:
.venv\\Scripts\\python.exe smoke\\smoke_probe.py
Deliberately uses kipy directly (not board_io) so it works even if
board_io has a bug. Prints: version, board path, selection contents,
shapes summary, zones + filled polygon stats, stackup table.
"""
import sys
import traceback
from kipy import KiCad
from kipy.board_types import BoardRectangle, BoardShape
from kipy.proto.board.board_pb2 import BoardStackupLayerType
from kipy.proto.board.board_types_pb2 import ZoneType
from kipy.util.board_layer import canonical_name
def mm(nm):
return nm / 1e6
def main():
print("== connect ==")
kicad = KiCad()
kicad.ping()
print("version:", kicad.get_version())
try:
print("check_version:", kicad.check_version())
except Exception as e:
print("check_version raised:", e)
board = kicad.get_board()
print("board.name:", board.name)
print("board_filename:", getattr(board.document, "board_filename", "?"))
print("\n== selection ==")
sel = list(board.get_selection())
print(f"{len(sel)} item(s) selected")
for item in sel:
line = f" {type(item).__name__}"
if isinstance(item, BoardRectangle):
tl, br = item.top_left, item.bottom_right
line += (f" layer={canonical_name(item.layer)}"
f" tl=({mm(tl.x):.2f}, {mm(tl.y):.2f})mm"
f" br=({mm(br.x):.2f}, {mm(br.y):.2f})mm")
elif isinstance(item, BoardShape):
line += f" layer={canonical_name(item.layer)}"
print(line)
print("\n== shapes (board-wide rectangles) ==")
shapes = list(board.get_shapes())
rect_shapes = [s for s in shapes if isinstance(s, BoardRectangle)]
print(f"{len(shapes)} shapes total, {len(rect_shapes)} rectangles")
for s in rect_shapes[:20]:
tl, br = s.top_left, s.bottom_right
print(f" rect on {canonical_name(s.layer)}: "
f"({mm(tl.x):.2f}, {mm(tl.y):.2f}) - ({mm(br.x):.2f}, {mm(br.y):.2f}) mm")
print("\n== zones ==")
for zone in board.get_zones():
ztype = ZoneType.Name(zone.type)
net = zone.net.name if zone.net is not None else "<none>"
layers = [canonical_name(l) for l in zone.layers]
print(f" zone '{zone.name}' type={ztype} net={net} "
f"layers={layers} filled={zone.filled}")
try:
for layer, polys in zone.filled_polygons.items():
narcs = 0
nnodes = 0
nholes = 0
for p in polys:
nnodes += len(p.outline.nodes)
nholes += len(p.holes)
narcs += sum(1 for n in p.outline.nodes if n.has_arc)
print(f" fill on {canonical_name(layer)}: {len(polys)} "
f"poly(s), {nnodes} outline nodes, {nholes} holes, "
f"{narcs} arc nodes")
except Exception:
print(" filled_polygons FAILED:")
traceback.print_exc()
print("\n== stackup ==")
try:
for sl in board.get_stackup().layers:
tname = BoardStackupLayerType.Name(sl.type)
lname = canonical_name(sl.layer) if sl.type == \
BoardStackupLayerType.BSLT_COPPER else "-"
print(f" {tname:18s} layer={lname:8s} thickness={sl.thickness} nm"
f" enabled={sl.enabled}")
except Exception:
traceback.print_exc()
print("\nsmoke probe DONE")
return 0
if __name__ == "__main__":
sys.exit(main())