ShiboSoftwareDev/linux-gameboy-advance
Restores PCB assembly metadata by assigning part numbers and 3D CAD models, removing test-point bodies, and validating top-side SMT solder-paste features without altering copper or pads.
- Version
- 1.0.17
- License
- unset
- Stars
- 0
scripts/search-tail-route.mjs
import fs from "node:fs"
import {
checkDifferentNetViaSpacing,
checkEachPcbTraceNonOverlapping,
checkPadTraceClearance,
checkPcbTraceSelfShorts,
checkTracesAreContiguous,
checkViaPadClearance,
checkViaTraceClearance,
} from "@tscircuit/checks"
import {
getFullConnectivityMapFromCircuitJson,
PcbConnectivityMap,
} from "circuit-json-to-connectivity-map"
const routeFile = process.argv[2] ?? "work/optimized-carrier-traces.json"
const circuit = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
const cache = JSON.parse(fs.readFileSync("carrier-route-cache.json", "utf8"))
const traces = JSON.parse(fs.readFileSync(routeFile, "utf8"))
const carrierIds = new Set(cache.traces.map((trace) => trace.pcb_trace_id))
const routeById = new Map(traces.map((trace) => [trace.pcb_trace_id, trace.route]))
const targetId = "source_trace_124_0"
const target = circuit.find((element) =>
element.type === "pcb_trace" && element.pcb_trace_id === targetId)
for (const trace of circuit.filter((element) => element.type === "pcb_trace")) {
const route = routeById.get(trace.pcb_trace_id)
if (route) trace.route = structuredClone(route)
}
const oldViaKey = new Map(circuit.filter((element) =>
element.type === "pcb_via" && carrierIds.has(element.pcb_trace_id))
.map((via) => [via.pcb_trace_id, via.subcircuit_connectivity_map_key]))
const board = circuit.filter((element) =>
element.type !== "pcb_copper_pour" &&
!(element.type === "pcb_via" && carrierIds.has(element.pcb_trace_id)))
const startIndex = 27
const prefix = target.route.slice(0, startIndex + 1)
const start = prefix.at(-1)
const end = target.route.at(-1)
const originalTopStub = target.route.slice(33)
const wire = (x, y, layer) => ({ route_type: "wire", x, y, width: 0.12, layer })
const via = (x, y) => ({ route_type: "via", x, y,
from_layer: "inner1", to_layer: "top",
via_diameter: 0.45, via_hole_diameter: 0.3 })
const installVias = () => {
for (let index = board.length - 1; index >= 0; index--)
if (board[index].type === "pcb_via" && board[index].pcb_via_id.startsWith("tail_test_via_"))
board.splice(index, 1)
let viaIndex = 0
for (const trace of board.filter((element) =>
element.type === "pcb_trace" && carrierIds.has(element.pcb_trace_id))) {
for (const point of trace.route.filter((point) => point.route_type === "via")) {
board.push({
type: "pcb_via",
pcb_via_id: `tail_test_via_${viaIndex++}`,
pcb_trace_id: trace.pcb_trace_id,
x: point.x, y: point.y,
hole_diameter: 0.3, outer_diameter: 0.45,
layers: ["top", "inner1", "inner2", "bottom"],
from_layer: point.from_layer, to_layer: point.to_layer,
subcircuit_id: trace.subcircuit_id,
subcircuit_connectivity_map_key: oldViaKey.get(trace.pcb_trace_id),
})
}
}
}
const checks = [
checkViaTraceClearance,
checkViaPadClearance,
checkDifferentNetViaSpacing,
checkPadTraceClearance,
checkEachPcbTraceNonOverlapping,
checkPcbTraceSelfShorts,
checkTracesAreContiguous,
]
const evaluate = (route) => {
target.route = route
installVias()
const connMap = getFullConnectivityMapFromCircuitJson(board)
const pcbConnMap = new PcbConnectivityMap(board)
const options = {
connMap,
pcbConnectivityMap: pcbConnMap,
minClearance: 0.1,
}
const viaErrors = checks.slice(0, 3).flatMap((check) => check(board, options))
.filter((element) => element.type?.endsWith("_error"))
if (viaErrors.length) return viaErrors
const errors = checks.slice(3).flatMap((check) => check(board, options))
.filter((element) => element.type?.endsWith("_error"))
return errors
}
let attempts = 0
let best = null
const seen = new Set()
const tryRoute = (tail, label) => {
const route = [...prefix, ...tail]
const key = JSON.stringify(route.slice(prefix.length))
if (seen.has(key)) return false
seen.add(key)
attempts++
const errors = evaluate(route)
if (!best || errors.length < best.errors.length) {
best = { errors, route, label }
console.log(`attempt ${attempts}: ${errors.length} errors (${label})`)
for (const error of errors.slice(0, 5)) {
const viaElement = error.pcb_via_id
? board.find((element) => element.pcb_via_id === error.pcb_via_id)
: undefined
console.log(` ${error.type}: ${error.message} via=${JSON.stringify(viaElement)} ${JSON.stringify(error)}`)
}
}
if (errors.length) return false
target.route = route
const replacement = traces.find((trace) => trace.pcb_trace_id === targetId)
replacement.route = route
fs.writeFileSync(routeFile, JSON.stringify(traces, null, 2) + "\n")
console.log(`FOUND clean tail after ${attempts} attempts: ${label}`)
return true
}
// The existing route reaches this pin's vertical channel cleanly on inner1.
// Try staggered vias above the 0.5 mm connector fanout before a wider search.
const candidates = []
for (const x of [13.45])
for (const y of [33])
candidates.push({ x, y })
let found = false
for (const { x, y } of candidates) {
const shapes = [
[wire(x, y, "inner1"), via(x, y), wire(x, y, "top"),
...originalTopStub],
]
for (let shape = 0; shape < shapes.length; shape++) {
if (tryRoute(shapes[shape], `via ${x},${y} shape ${shape}`)) {
found = true
break
}
}
if (found) break
}
if (best?.errors.length) {
console.error(`No clean tail in ${attempts} attempts; best ${best.errors.length} errors (${best.label})`)
process.exitCode = 1
}