R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发

- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base)
- 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base)
- 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、
  东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base)
- 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面
- 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、
  J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder
  开关(route_base、remote_route.ps1);剩 37 个网待手工补
- 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base);
  已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查)
- 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
zhou yang
2026-09-10 21:02:10 +08:00
共同撰写人 Claude Opus 5
父节点 18d3d69856
当前提交 efcba30a0c
共修改 280 个文件,包含 92007 行新增和 15053 行删除
+163
查看文件
@@ -0,0 +1,163 @@
param(
[string]$Server = '10.5.5.13',
[int]$Passes = 1,
[switch]$PriorityCache,
[switch]$DpOnly,
# 服务器并行试算:每个元素是一组额外参数(如 '--repair --bot-pen-w 1'),
# 各自在独立副本里跑,取未完成最少的那个走下面同一套校验 / 接受逻辑。
[string[]]$Variants = @(),
[int]$PollSec = 60,
# 服务器上跑布线的解释器;PyPy 见 PROGRESS(纯 Python 的 A*,JIT 通常快数倍)
[string]$Interp = 'python3'
)
$ErrorActionPreference = 'Stop'
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
$Repo = Resolve-Path (Join-Path $Here '..\..')
$Python = 'C:\Python314\python.exe'
$Ssh = 'C:\Windows\System32\OpenSSH\ssh.exe'
$Scp = 'C:\Windows\System32\OpenSSH\scp.exe'
# 临时文件带 PID:允许同时起两批(快的单轮变体 + 慢的拆线重布),互不覆盖
$Archive = Join-Path ([System.IO.Path]::GetTempPath()) "dcdc-remote-route-$PID.tgz"
$RunScript = Join-Path ([System.IO.Path]::GetTempPath()) "dcdc-remote-run-$PID.sh"
$RemoteArchive = Split-Path -Leaf $Archive
$Incoming = Join-Path $Here "_base_routes.server.$PID.json"
$Cache = Join-Path $Here '_base_routes.json'
$Backup = Join-Path $Here '_base_routes.pre-server.json'
$Rejected = Join-Path $Here '_base_routes.rejected.json'
$remoteRoot = $null
if ($Passes -lt 1) {
throw 'Passes must be at least 1.'
}
if ($DpOnly -and $Variants.Count -gt 0) {
throw '-DpOnly cannot be combined with -Variants.'
}
Push-Location $Repo
try {
& $Python '.claude\r2\route_base.py' --snapshot-vias
if ($LASTEXITCODE -ne 0) { throw 'Failed to refresh board-via snapshot.' }
$remoteRoot = (& $Ssh $Server 'mktemp -d /tmp/dcdc-route.XXXXXX').Trim()
if ($LASTEXITCODE -ne 0 -or $remoteRoot -notmatch '^/tmp/dcdc-route\.[A-Za-z0-9]+$') {
throw "Unexpected remote work directory: $remoteRoot"
}
& tar.exe --exclude=*.png --exclude=*.bak --exclude=__pycache__ `
--exclude=_remote_route.tgz -czf $Archive `
'.claude\r2' '.claude\eda.py' '.claude\pcb_pour.py' '.claude\eda-parts.json'
if ($LASTEXITCODE -ne 0) { throw 'Failed to build routing bundle.' }
& $Scp $Archive "${Server}:${remoteRoot}/"
if ($LASTEXITCODE -ne 0) { throw 'Failed to upload routing bundle.' }
& $Ssh $Server "tar -xzf ${remoteRoot}/${RemoteArchive} -C ${remoteRoot}"
if ($LASTEXITCODE -ne 0) { throw 'Failed to extract routing bundle.' }
$priority = if ($PriorityCache) { ' --priority-cache' } else { '' }
$scope = if ($DpOnly) { ' --dp-only' } else { '' }
$timeFmt = "/usr/bin/time -f 'REMOTE_TIME=%e sec CPU=%P RSS=%M KB'"
$remoteResult = "${remoteRoot}/.claude/r2/_base_routes.json"
if ($Variants.Count -eq 0) {
$command = "DCDC_ROUTE_SERVER=1 $timeFmt $Interp " +
"${remoteRoot}/.claude/r2/route_base.py --offline --replan --passes $Passes$priority$scope"
& $Ssh $Server $command
if ($LASTEXITCODE -ne 0) { throw 'Remote routing failed.' }
}
else {
# 变体各自一份副本(缓存文件同名,不能共用目录),脱离 ssh 会话在后台跑:
# 拆线重布可能要跑很久,ssh 断线不能把计算带走。
$lines = @('#!/bin/bash', "cd ${remoteRoot}")
for ($k = 0; $k -lt $Variants.Count; $k++) {
$v = $Variants[$k]
$lines += "mkdir -p v$k && cp -r .claude v$k/"
$lines += "( DCDC_ROUTE_SERVER=1 $timeFmt $Interp ${remoteRoot}/v$k/.claude/r2/route_base.py " +
"--offline --replan --passes $Passes$priority $v > v$k.log 2>&1; echo `$? > v$k.rc ) &"
}
$lines += 'wait'
$lines += 'touch done'
[System.IO.File]::WriteAllText($RunScript, (($lines -join "`n") + "`n"))
& $Scp $RunScript "${Server}:${remoteRoot}/run.sh"
if ($LASTEXITCODE -ne 0) { throw 'Failed to upload run script.' }
& $Ssh $Server "setsid nohup bash ${remoteRoot}/run.sh > /dev/null 2>&1 < /dev/null &"
if ($LASTEXITCODE -ne 0) { throw 'Failed to start remote variants.' }
for ($k = 0; $k -lt $Variants.Count; $k++) { Write-Host "v${k}: $($Variants[$k])" }
$start = Get-Date
$poll = 0
while ($true) {
Start-Sleep -Seconds $PollSec
$poll++
$state = (& $Ssh $Server "test -f ${remoteRoot}/done && echo DONE || echo RUN").Trim()
if ($state -eq 'DONE') { break }
if ($poll % 5 -eq 0) {
$mins = [int]((Get-Date) - $start).TotalMinutes
Write-Host "--- $mins min ---"
& $Ssh $Server "cd ${remoteRoot} && for f in v*.log; do echo `"`$f: `$(tail -n 1 `$f)`"; done"
}
}
$summary = & $Ssh $Server ("cd ${remoteRoot} && for k in `$(seq 0 $($Variants.Count - 1)); do " +
"echo `"v`$k rc=`$(cat v`$k.rc) `$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); " +
"print(len(d[\`"logs\`"]), len(d[\`"segs\`"]), len(d[\`"vias\`"]))' " +
"v`$k/.claude/r2/_base_routes.json 2>/dev/null || echo NA)`"; done")
$best = $null
foreach ($row in $summary) {
Write-Host $row
if ($row -match '^v(\d+) rc=0 (\d+) (\d+) (\d+)$') {
$cand = @{ K = [int]$Matches[1]; Logs = [int]$Matches[2]; Vias = [int]$Matches[4] }
if (-not $best -or $cand.Logs -lt $best.Logs -or
($cand.Logs -eq $best.Logs -and $cand.Vias -lt $best.Vias)) {
$best = $cand
}
}
}
for ($k = 0; $k -lt $Variants.Count; $k++) {
Write-Host "===== v${k}: $($Variants[$k]) ====="
& $Ssh $Server "grep -E '拆线重布|第 [0-9]+ 轮|REMOTE_TIME|Traceback|Error' ${remoteRoot}/v$k.log | tail -n 25"
}
if (-not $best) { throw 'No remote variant finished successfully.' }
Write-Host "Best variant: v$($best.K) ($($Variants[$best.K])) with $($best.Logs) unfinished."
$remoteResult = "${remoteRoot}/v$($best.K)/.claude/r2/_base_routes.json"
}
if ($DpOnly) {
Write-Host 'Server-side D+/- diagnostic completed; local route cache was not changed.'
}
else {
& $Scp "${Server}:${remoteResult}" $Incoming
if ($LASTEXITCODE -ne 0) { throw 'Failed to download routing result.' }
$result = Get-Content -LiteralPath $Incoming -Raw | ConvertFrom-Json
if ($result.schema -ne 7 -or $result.slot_rot -ne 0 -or
$result.usb_slot_ref.'1' -ne 'J34' -or $result.usb_slot_ref.'5' -ne 'J38') {
throw 'Downloaded routing cache failed topology validation.'
}
$current = if (Test-Path -LiteralPath $Cache) {
Get-Content -LiteralPath $Cache -Raw | ConvertFrom-Json
} else { $null }
if ($current -and $result.logs.Count -gt $current.logs.Count) {
Move-Item -LiteralPath $Incoming -Destination $Rejected -Force
Write-Host "Rejected worse server result: $($result.logs.Count) unfinished > current $($current.logs.Count)."
}
else {
if ($current) {
Copy-Item -LiteralPath $Cache -Destination $Backup -Force
}
Move-Item -LiteralPath $Incoming -Destination $Cache -Force
Write-Host "Accepted server result: $($result.segs.Count) segments, $($result.vias.Count) vias, $($result.logs.Count) unfinished."
}
}
}
finally {
Pop-Location
foreach ($f in @($Archive, $RunScript)) {
if (Test-Path -LiteralPath $f) { Remove-Item -LiteralPath $f -Force }
}
if ($remoteRoot -and $remoteRoot -match '^/tmp/dcdc-route\.[A-Za-z0-9]+$') {
# ⛔ 模式里的 `(v[0-9]+/)?[.]claude` 是故意的:直接写目录串的话,执行这条命令的
# 远程 shell 命令行里也含这个串,pkill 先把自己杀了(ssh 返回 255),后面的 rm
# 根本不执行。按路径匹配而不是按 `python3`,换成 pypy3 也杀得到。
& $Ssh $Server "pkill -f '${remoteRoot}/(v[0-9]+/)?[.]claude/r2/route_base' ; rm -rf -- '${remoteRoot}'"
}
}