92bb29637f
Tracks are now first-class Problem objects (TrackSeg: centerline + width, dump schema v5), so the wide/narrow decision replays at raster time: traces at least TRACK_1D_FACTOR (3) cells wide rasterize from their outline as before; narrower ones mark the cells their centerline crosses as copper and connect them with explicit conductance links carrying the trace's TRUE arc length per link - no staircase inflation for diagonals or arcs, and no discretization error in the trace R, at any grid size. Links across cells already joined by pour faces are skipped (union, not sum); chain-only cells get no sheet faces (their copper is narrower than a cell). Electrodes, via barrels, connectivity restriction and the skin-effect scaling all work on chain cells unchanged. This removes the need to shrink the cell size for thin traces: a 0.2 mm bridge at 500 um cells now matches its finely-rasterized ground truth within a few percent (tested), including diagonal and arc traces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
"""Top-level orchestration for the KiCad-launched action.
|
||
|
||
Flow: connect -> read the two selected contacts (rectangles/pads) ->
|
||
gather fills -> selection dialog (net, layers, contacts, current, cell)
|
||
-> extract vias -> solve -> figures + report.
|
||
|
||
Every failure is reported twice: on stdout (lands in the KiCad status-bar
|
||
warning list) and as a matplotlib error figure, so it cannot be missed.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import traceback
|
||
|
||
from . import config, pipeline, report
|
||
from .errors import CandidateError, UserFacingError
|
||
|
||
|
||
def _fail(message: str, outdir) -> None:
|
||
print(f"ERROR: {message}")
|
||
from . import plots
|
||
fig = plots.fig_error(message)
|
||
plots.save_and_show([(fig, "error")], outdir)
|
||
sys.exit(1)
|
||
|
||
|
||
def main() -> None:
|
||
outdir = None
|
||
try:
|
||
from kipy.errors import ApiError
|
||
|
||
from . import board_io, dialog
|
||
try:
|
||
kicad, board = board_io.connect()
|
||
stackup = board_io.get_stackup_info(board)
|
||
es1, es2, net_hint = board_io.get_electrodes(board)
|
||
if board_io.any_zone_unfilled(board) or config.ALWAYS_REFILL:
|
||
board_io.refill(board)
|
||
fills = board_io.gather_net_fills(board)
|
||
tracks = board_io.gather_net_tracks(board)
|
||
copper = board_io.merge_copper(
|
||
fills, board_io.tracks_as_polygons(tracks))
|
||
candidate_nets = board_io.nets_overlapping(copper, es1, es2)
|
||
buildups = board_io.gather_mask_buildups(board)
|
||
except ApiError as e:
|
||
raise UserFacingError(
|
||
f"KiCad API error: {e}\nIf KiCad is showing a dialog, close "
|
||
f"it and run again."
|
||
)
|
||
|
||
if not candidate_nets:
|
||
raise CandidateError(
|
||
"No copper (zone fill or trace) overlaps both contacts. "
|
||
"Check that both sit over copper of the same net and that "
|
||
"the fills are up to date (press B in the board editor)."
|
||
)
|
||
|
||
def group_label(parts):
|
||
names = [p.label for p in parts[:3]]
|
||
more = f" +{len(parts) - 3}" if len(parts) > 3 else ""
|
||
return f"{len(parts)}× " + ", ".join(names) + more
|
||
|
||
def group_contact(parts):
|
||
contacts = {p.contact for p in parts}
|
||
return contacts.pop() if len(contacts) == 1 else "auto"
|
||
|
||
default_net = (net_hint if net_hint in candidate_nets
|
||
else candidate_nets[0])
|
||
selection = dialog.ask(
|
||
candidates={n: list(copper[n].keys()) for n in candidate_nets},
|
||
layer_order=stackup.names,
|
||
default_net=default_net,
|
||
e1_label=group_label(es1), e2_label=group_label(es2),
|
||
contact1=group_contact(es1), contact2=group_contact(es2),
|
||
buildup_layers=sorted(buildups.keys()),
|
||
)
|
||
if selection is None:
|
||
print("cancelled")
|
||
return
|
||
|
||
if selection.contact1 != "auto":
|
||
for e in es1:
|
||
e.contact = selection.contact1
|
||
if selection.contact2 != "auto":
|
||
for e in es2:
|
||
e.contact = selection.contact2
|
||
if selection.cell_um is not None:
|
||
config.CELL_UM_OVERRIDE = selection.cell_um
|
||
|
||
try:
|
||
problem = board_io.build_problem(
|
||
board, selection.net, selection.layers, es1, es2, stackup,
|
||
fills,
|
||
buildups=(buildups if selection.include_buildup else None),
|
||
extra_cu_um=selection.extra_cu_um,
|
||
tracks=(tracks if selection.include_tracks else None))
|
||
outdir = report.make_output_dir(board_io.board_dir(board))
|
||
except ApiError as e:
|
||
raise UserFacingError(f"KiCad API error: {e}")
|
||
|
||
report.write_geometry_dump(outdir, problem)
|
||
pipeline.run(problem, outdir, show=True, i_test=selection.current_a,
|
||
freq_hz=selection.freq_hz,
|
||
contact_model=selection.contact_model)
|
||
except UserFacingError as e:
|
||
_fail(str(e), outdir)
|
||
except Exception:
|
||
_fail(traceback.format_exc(), outdir)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|