"""Find free spots for parts added in a revision: a spiral search around each part's preferred position (from design.py), trying 4 rotations, keeping courtyards apart, inside the board and out of the antenna keep-out. Prints the positions to paste into design.py.""" import math import sys import os sys.path.insert(0, os.path.dirname(__file__)) import design as D from board import build GAP = 0.25 # min gap between courtyards, mm KEEPOUT = (18.75, 56.9, 67.25, D.BOARD_H) # ESP32 antenna EDGE = 0.6 def overlaps(a, b, gap=GAP): return min(a[2], b[2]) - max(a[0], b[0]) > -gap and min(a[3], b[3]) - max(a[1], b[1]) > -gap def legal(part, fixed): for r in part.court_rects(): if r[0] < EDGE or r[1] < EDGE or r[2] > D.BOARD_W - EDGE or r[3] > D.BOARD_H - EDGE: return False if overlaps(r, KEEPOUT, 0): return False if any(overlaps(r, f) for f in fixed): return False return True def place(refs, parts): fixed = [r for ref, p in parts.items() if ref not in refs for r in p.court_rects()] out = {} for ref in refs: p = parts[ref] x0, y0, rot0 = p.x, p.y, p.rot best = None for step in range(0, 80): # radius in 0.25 mm steps, up to 20 mm rad = step * 0.25 n = max(1, int(2 * math.pi * rad / 0.25)) for i in range(n): a = 2 * math.pi * i / n x, y = round(x0 + rad * math.cos(a), 2), round(y0 + rad * math.sin(a), 2) for rot in (rot0, (rot0 + 90) % 360, (rot0 + 180) % 360, (rot0 + 270) % 360): p.x, p.y, p.rot = x, y, rot if legal(p, fixed): best = (x, y, rot, rad) break if best: break if best: break if not best: print(f'{ref}: no spot within 20 mm') p.x, p.y, p.rot = x0, y0, rot0 continue p.x, p.y, p.rot = best[:3] fixed += p.court_rects() out[ref] = best print(f'{ref}: ({best[0]}, {best[1]}) rot {best[2]} moved {best[3]:.2f} mm from preferred') return out if __name__ == '__main__': parts = build() place(sys.argv[1:], parts)