文件
phonerail/.claude/r2/remote_blade.sh
T

145 行
7.9 KiB
Bash
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/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<k>.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<k>.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<k; i++)); do
$SCP -q "$SERVER:$R/v$i/.claude/r2/_blade_routes.json" ".claude/r2/_blade_routes.var$i.json" || echo "v$i 没有结果"
$SCP -q "$SERVER:$R/v$i.log" ".claude/r2/_blade_routes.var$i.log" || true
echo "===== v$i rc=$($SSH "$SERVER" "cat $R/v$i.rc" | tr -d '\r')"
grep -E 'REMOTE_TIME|第 [0-9]+ 轮|D± 构造|Traceback|Error' ".claude/r2/_blade_routes.var$i.log" | tail -n 12 || true
[ -f ".claude/r2/_blade_routes.var$i.json" ] && "$PY" -c "
import json; d=json.load(open('.claude/r2/_blade_routes.var$i.json',encoding='utf-8'))
print('v$i: 没搞定 %d / 段 %d / 孔 %d' % (len(d['logs']), len(d['segs']), len(d['vias'])))
for m in d['logs'][:30]: print(' ', m)"
done