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
}