main
John Lauer Publish 0.2.56 73fcf19
"""Issues #153 and #163 through the CLI: `silkscreen-audit --board` builds its own input from the saved board
(a 45 degree label beside a round test point is not a collision; a label on a pad is a finding, exit 0), and
`silkscreen fab-check` / `fab-tidy` report and apply the Fab layer standard without a run.

  python3 tests/silk-fab-workflow.py <abs path to target/release/adom-aiflow>
"""
import json, os, subprocess, sys, tempfile
from pathlib import Path

BIN = sys.argv[1]
ENV = dict(os.environ, AIFLOW_DASHBOARD='off', AIFLOW_NO_PART_SEARCH='1', AIFLOW_NO_PART_LOOKUP='1')

BOARD = '''(kicad_pcb (version 20241229) (generator "pcbnew") (net 0 "") (layers (0 "F.Cu" signal) (31 "B.Cu" signal))
 (gr_rect (start 0 0) (end 30 30) (layer "Edge.Cuts"))
 (footprint "TestPoint:TP" (layer "F.Cu") (at 10 10 0)
  (property "Reference" "TP1" (at 1.5 -1.5 45) (layer "F.SilkS") (uuid "t1") (effects (font (size 0.8 0.8) (thickness 0.12))))
  (property "Value" "TP 3V3" (at 0 4 0) (layer "F.Fab") (uuid "t2") (effects (font (size 1 1) (thickness 0.15))))
  (fp_text user "${REFERENCE}" (at 0 2.5 0) (layer "F.Fab") (uuid "t3") (effects (font (size 0.26 0.26) (thickness 0.04))))
  (fp_circle (center 0 0) (end 1 0) (stroke (width 0.1) (type solid)) (fill no) (layer "F.Fab"))
  (fp_circle (center 0 0) (end 1.5 0) (stroke (width 0.05) (type solid)) (fill no) (layer "F.CrtYd"))
  (pad "1" smd circle (at 0 0) (size 2 2) (layers "F.Cu" "F.Mask")))
 (footprint "R:R_0805" (layer "F.Cu") (at 20 20 0)
  (property "Reference" "R1" (at 0 0 0) (layer "F.SilkS") (uuid "r1") (effects (font (size 0.8 0.8) (thickness 0.12))))
  (property "Value" "10k" (at 0 1.6 0) (layer "F.Fab") (uuid "r2") (effects (font (size 1 1) (thickness 0.15))))
  (fp_rect (start -1 -0.625) (end 1 0.625) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
  (fp_rect (start -1.68 -0.95) (end 1.68 0.95) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))
  (pad "1" smd roundrect (at -0.9 0) (size 1 1.45) (layers "F.Cu" "F.Mask"))
  (pad "2" smd roundrect (at 0.9 0) (size 1 1.45) (layers "F.Cu" "F.Mask")))
 (via (at 25 5) (size 0.6) (drill 0.3) (layers "F.Cu" "B.Cu") (net 0)))
'''


def call(*args, cwd=None):
    return subprocess.run([BIN, '--ai-thread', 'silk-fab-test', *args], capture_output=True, text=True, env=ENV, cwd=cwd)


with tempfile.TemporaryDirectory() as tmp:
    p = Path(tmp)
    board = p / 'b.kicad_pcb'
    board.write_text(BOARD)
    # --- #153: the input is built from the board; R1's label sits on its own pads (a real collision), TP1's 45 degree label does not
    r = call('--run', str(p / 'norun'), 'silkscreen-audit', '--board', str(board), '--out', str(p / 'res.json'))
    out = r.stdout + r.stderr
    assert r.returncode == 0 and 'FINDINGS' in out and 'rectangle check' in out, out
    inp = json.loads((p / 'silkscreen-audit-input.json').read_text())
    assert {i['id'] for i in inp['inventory']} == {'TP1', 'R1', 'via0'}, inp['inventory']
    res = json.loads((p / 'res.json').read_text())
    hits = {(c['mark'], c['obstacle']) for c in res['collisions']}
    assert ('R1-reference', 'R1') in hits, hits
    assert not any(m.startswith('TP1') and o == 'TP1' for m, o in hits), hits
    # move R1's label off the part: the audit passes
    board.write_text(BOARD.replace('(property "Reference" "R1" (at 0 0 0)', '(property "Reference" "R1" (at 0 -2.2 0)'))
    r = call('--run', str(p / 'norun'), 'silkscreen-audit', '--board', str(board), '--out', str(p / 'res.json'))
    assert r.returncode == 0 and r.stdout.startswith('OK'), r.stdout + r.stderr
    # --- #163: fab-check names the sprawl; fab-tidy writes a copy that meets the standard
    r = call('--run', str(p / 'norun'), 'silkscreen', 'fab-check', '--board', str(board))
    out = r.stdout + r.stderr
    assert r.returncode == 0 and 'FINDINGS' in out and 'value(s) shown on the Fab layer' in out and 'fab-tidy' in out, out
    r = call('--run', str(p / 'norun'), 'silkscreen', 'fab-tidy', '--board', str(board), '--size', '1')
    out = r.stdout + r.stderr
    tidied = p / 'b-fab.kicad_pcb'
    assert r.returncode == 0 and tidied.is_file() and 'adopt-board' in out, out
    assert board.read_text() != tidied.read_text() and '(at 0 -2.2 0) (layer "F.SilkS")' in tidied.read_text()
    r = call('--run', str(p / 'norun'), 'silkscreen', 'fab-check', '--board', str(tidied))
    assert r.returncode == 0 and r.stdout.startswith('OK: Fab layer meets the standard'), r.stdout + r.stderr

print('PASS: silkscreen-audit builds its input from the board (45 degree label by a round pad clear, a label on its pads a finding); fab-check names the Fab sprawl and fab-tidy fixes it')