- KiCad project generated by pcb/tools (design, route, schematic, fab outputs). - v0.4 fixes the v0.3 review findings: low-dropout 3.3 V regulator, OR'd 5 V supply, polyfuse, USB ESD protection, input clamps, ceramic decoupling at the drivers, A4988/DRV8825 jumper, ground stitching. DRC/ERC clean. - v0.3 (as ordered) kept in pcb/archive/v0.3. - FluidNC config.yaml for the board; rho hard limit on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""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)
|