#!/bin/bash # 刀片 PCB 的布线 / 扫孔位上服务器(CLAUDE.md:所有布线运算只在 10.5.5.13 跑;底板的入口是 remote_route.ps1)。 # # bash .claude/r2/remote_blade.sh # 布线,一组默认变体 # bash .claude/r2/remote_blade.sh '' '--pf' '--viafan' # 每个参数 = 一组变体,服务器上并行跑 # MODE=stitch bash .claude/r2/remote_blade.sh # GND 缝合孔落点(先抓板上导线快照) # # 布线结果拉回 .claude/r2/_blade_routes.var.json / .log,**不自动覆盖** _blade_routes.json —— # 比完(没搞定条数 + router_check 净距)再手动拷成缓存,然后 router.py --push。 # 缝合孔落点拉回 .claude/r2/_blade_stitch_pts.json,然后 pcb_route.py --stitch 下发。 # 环境变量:MODE(route / stitch)、SERVER(默认 10.5.5.13)、INTERP(默认 PyPy)、POLL(秒)。 set -euo pipefail MODE=${MODE:-route} SERVER=${SERVER:-10.5.5.13} INTERP=${INTERP:-/root/pypy/bin/pypy3} POLL=${POLL:-30} # ServerAlive / ConnectTimeout:2026-09-15 走公网时轮询的 ssh 会无声挂死(远端早跑完了,本机一直等) SSHOPT='-o ConnectTimeout=15 -o ServerAliveInterval=10 -o ServerAliveCountMax=3' SSH="/c/Windows/System32/OpenSSH/ssh.exe $SSHOPT" SCP="/c/Windows/System32/OpenSSH/scp.exe $SSHOPT" PY=/c/Python314/python REPO=$(cd "$(dirname "$0")/../.." && pwd) cd "$REPO" [ $# -eq 0 ] && set -- '' # 1. 板面快照(服务器上没有桥接):布线要过孔,扫缝合孔要导线 # NOSNAP=1:不抓板,直接用本地现成的快照文件(2026-09-11 晚:摆位还没下发、板上是旧铜时离线试布线用, # 先把 _blade_board_vias.json 写成 [] 或新摆位该有的孔) if [ "${NOSNAP:-0}" = 1 ]; then echo "NOSNAP=1:跳过板面快照,用本地 _blade_board_vias.json / _blade_board_tracks.json" elif [ "$MODE" = stitch ]; then "$PY" .claude/r2/pcb_route.py --snapshot-tracks else "$PY" .claude/r2/router.py --snapshot-vias fi # 2. 打包:刀片只要 r2 的脚本和刀片缓存 + 上一级的 eda.py / eda-parts.json / mi8_mech.json TMP=${TMPDIR:-/tmp} TGZ=$(mktemp -u "$TMP/blade-route-XXXXXX").tgz RUNSH=$(mktemp -u "$TMP/blade-run-XXXXXX").sh tar --exclude='*.png' --exclude='*.bak' --exclude='__pycache__' --exclude='_base_*' \ --exclude='archive_base2L' --exclude='_blade_routes.var*' -czf "$TGZ" \ .claude/r2 .claude/eda.py .claude/eda-parts.json .claude/mi8_mech.json R=$($SSH "$SERVER" 'mktemp -d /tmp/dcdc-blade.XXXXXX' | tr -d '\r') case "$R" in /tmp/dcdc-blade.*) ;; *) echo "远程目录不对: $R"; exit 1 ;; esac cleanup() { rm -f "$TGZ" "$RUNSH" # ⚠ 模式里的 `[.]claude` 是故意的:直接写目录串的话 pkill 会先把执行这条命令的远程 shell 自己杀掉 $SSH "$SERVER" "pkill -f '$R/v[0-9]+/[.]claude/r2/router' ; rm -rf -- '$R'" || true } trap cleanup EXIT $SCP -q "$(cygpath -w "$TGZ")" "$SERVER:$R/b.tgz" $SSH "$SERVER" "tar -xzf $R/b.tgz -C $R" if [ "$MODE" = stitch ]; then $SSH "$SERVER" "cd $R && DCDC_ROUTE_SERVER=1 $INTERP .claude/r2/pcb_route.py --stitch-plan" $SCP -q "$SERVER:$R/.claude/r2/_blade_stitch_pts.json" .claude/r2/_blade_stitch_pts.json echo "已拉回 .claude/r2/_blade_stitch_pts.json,接着:python .claude/r2/pcb_route.py --stitch" exit 0 fi # MODE=gndvia(2026-09-14):在 _blade_routes.json + 缝合孔之上只给够不着铺铜的 GND 脚打孔,拉回 _blade_routes.gndvia.json if [ "$MODE" = gndvia ]; then $SSH "$SERVER" "cd $R && DCDC_ROUTE_SERVER=1 $INTERP .claude/r2/gndvia_blade1.py" $SCP -q "$SERVER:$R/.claude/r2/_blade_routes.gndvia.json" .claude/r2/_blade_routes.gndvia.json echo "已拉回 .claude/r2/_blade_routes.gndvia.json" exit 0 fi # MODE=gndtie(2026-09-14):参数 = GND 焊盘「位号.脚号」,给 DRC 报断开的地脚找引出线接已有 GND 孔或新打孔,拉回 _blade_routes.gndtie.json if [ "$MODE" = gndtie ]; then $SSH "$SERVER" "cd $R && DCDC_ROUTE_SERVER=1 $INTERP .claude/r2/gndtie_blade1.py $*" $SCP -q "$SERVER:$R/.claude/r2/_blade_routes.gndtie.json" .claude/r2/_blade_routes.gndtie.json echo "已拉回 .claude/r2/_blade_routes.gndtie.json" exit 0 fi # MODE=opt(2026-09-15):_blade_routes.json(已拆掉要重布的网)之上按 ESC 孔 + 0.15 线宽重布; # 参数 = 每种顺序一个「网,网,...」(`$` 写成 `@`),拉回 _blade_routes.opt.json if [ "$MODE" = opt ]; then q=""; for a in "$@"; do q="$q '$a'"; done $SSH "$SERVER" "cd $R && DCDC_ROUTE_SERVER=1 timeout 240 $INTERP .claude/r2/opt_blade1.py $q" for ((i=0; i<$#; i++)); do $SCP -q "$SERVER:$R/.claude/r2/_blade_routes.opt$i.json" .claude/r2/_blade_routes.opt$i.json || echo "opt$i 没有结果"; done exit 0 fi # MODE=legs(2026-09-15):参数 = .claude/r2 下的 spec json(见 opt_legs.py),拉回 spec 里 out 指定的每个变体 if [ "$MODE" = legs ]; then $SSH "$SERVER" "cd $R && DCDC_ROUTE_SERVER=1 timeout 300 $INTERP .claude/r2/opt_legs.py .claude/r2/$1" for f in $($SSH "$SERVER" "cd $R/.claude/r2 && ls _blade_routes.legs*.json" | tr -d ' '); do $SCP -q "$SERVER:$R/.claude/r2/$f" .claude/r2/$f; done exit 0 fi # MODE=repair:在 _blade_routes.json 的铜之上只补这几个网(参数 = 网名,`$` 写成 `@`), # 结果拉回 _blade_routes.repaired.json(合并后的完整缓存),比过再拷成 _blade_routes.json if [ "$MODE" = repair ]; then [ -n "$*" ] || { echo 'MODE=repair 要给网名'; exit 1; } $SSH "$SERVER" "cd $R && DCDC_ROUTE_SERVER=1 $INTERP .claude/r2/repair_route.py --cache $*" $SCP -q "$SERVER:$R/.claude/r2/_blade_routes.repaired.json" .claude/r2/_blade_routes.repaired.json echo "已拉回 .claude/r2/_blade_routes.repaired.json" exit 0 fi # 3. 每个变体一份副本(缓存同名,不能共用目录),脱离 ssh 会话后台跑,全部结束写 done。 # ⛔ run.sh 必须**先整个传上去、再另起一次 ssh 启动**:写成 `cat > run.sh && setsid … &` 的话, # 结尾的 `&` 把整条 && 链(连 cat)一起放后台,ssh 一返回 stdin 就关了 ⇒ run.sh 是 0 字节, # 一个变体都没起、done 永远不出现(2026-09-11 第一次就这么空转了 2 分钟)。 { echo '#!/bin/bash' echo "cd $R" k=0 for v in "$@"; do echo "mkdir -p v$k && cp -r .claude v$k/" echo "( cd v$k && DCDC_ROUTE_SERVER=1 /usr/bin/time -f 'REMOTE_TIME=%e sec RSS=%M KB' $INTERP .claude/r2/router.py --replan $v > ../v$k.log 2>&1; echo \$? > ../v$k.rc ) &" k=$((k+1)) done echo 'wait' echo 'touch done' } > "$RUNSH" k=0 for v in "$@"; do echo "v$k: '$v'"; k=$((k+1)); done $SCP -q "$(cygpath -w "$RUNSH")" "$SERVER:$R/run.sh" [ "$($SSH "$SERVER" "wc -c < $R/run.sh" | tr -d '\r ')" -gt 20 ] || { echo 'run.sh 没传上去'; exit 1; } $SSH "$SERVER" "setsid nohup bash $R/run.sh > /dev/null 2>&1 < /dev/null &" sleep 5 $SSH "$SERVER" "ls $R/v*.log > /dev/null 2>&1" || { echo '5 秒后还没有 v*.log,变体没起来'; exit 1; } t0=$(date +%s) while ! $SSH "$SERVER" "test -f $R/done"; do sleep "$POLL" echo "--- $(( ($(date +%s) - t0) / 60 )) min: $($SSH "$SERVER" "cd $R && for f in v*.log; do printf '%s: ' \$f; tail -n 1 \$f; done" | tr '\n' ' ')" done # 4. 拉回每个变体的结果 + 日志,打印摘要(没搞定条数 / 段 / 孔) for ((i=0; i