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/route-backlight.mjs

import fs from "node:fs"

const soup = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
const step = 0.25
const layers = ["top", "inner1", "inner2", "bottom"]
const revisionTracePattern = /^(source_trace_36[1-9]|source_trace_37[0-1]|source_net_0_mst8[6-9]|source_net_6_mst4[3-9])_0$/
const oldTraces = soup.filter((e) => e.type === "pcb_trace" && !revisionTracePattern.test(e.pcb_trace_id ?? ""))
const pads = soup.filter((e) => e.type === "pcb_smtpad" || e.type === "pcb_plated_hole")
const vias = soup.filter((e) => e.type === "pcb_via" && !revisionTracePattern.test(e.pcb_trace_id ?? ""))
const holes = soup.filter((e) => e.type === "pcb_hole")
const ports = new Map(soup.filter((e) => e.type === "pcb_port").map((e) => [e.pcb_port_id, e]))

const plans = [
  { id: "source_trace_365_0", connection: "source_trace_365", from: "pcb_port_297", to: "pcb_port_554", width: 0.5, net: "OUT" },
  { id: "source_trace_366_0", connection: "source_trace_366", from: "pcb_port_550", to: "pcb_port_554", width: 0.12, net: "OUT" },
  { id: "source_trace_367_0", connection: "source_trace_367", from: "pcb_port_558", to: "pcb_port_554", width: 0.5, net: "OUT" },
  { id: "source_trace_369_0", connection: "source_trace_369", from: "pcb_port_298", to: "pcb_port_548", width: 0.12, net: "FB" },
  { id: "source_net_6_mst43_0", connection: "source_net_6", from: "pcb_port_528", to: "pcb_port_551", width: 0.5, net: "V3V3" },
  { id: "source_trace_361_0", connection: "source_trace_361", from: "pcb_port_556", to: "pcb_port_551", width: 0.5, net: "V3V3" },
  { id: "source_net_6_mst44_0", connection: "source_net_6", from: "pcb_port_551", to: "pcb_port_549", width: 0.5, net: "V3V3" },
  { id: "source_net_6_mst45_0", connection: "source_net_6", from: "pcb_port_551", to: "pcb_port_552", width: 0.5, net: "V3V3" },
  { id: "source_trace_363_0", connection: "source_trace_363", from: "pcb_port_553", to: "pcb_port_546", width: 0.5, net: "SW" },
  { id: "source_trace_364_0", connection: "source_trace_364", from: "pcb_port_555", to: "pcb_port_546", width: 0.5, net: "SW" },
  { id: "source_trace_370_0", connection: "source_trace_370", from: "pcb_port_548", to: "pcb_port_560", width: 0.12, net: "FB" },
  { id: "source_trace_371_0", connection: "source_trace_371", from: "pcb_port_561", to: "pcb_port_562", width: 0.12, net: "SET" },
  { id: "source_net_0_mst86_0", connection: "source_net_0", from: "pcb_port_399", to: "pcb_port_547", width: 0.5, net: "GND" },
  { id: "source_net_0_mst87_0", connection: "source_net_0", from: "pcb_port_533", to: "pcb_port_557", width: 0.5, net: "GND" },
  { id: "source_net_0_mst88_0", connection: "source_net_0", from: "pcb_port_379", to: "pcb_port_559", width: 0.5, net: "GND" },
  { id: "source_net_0_mst89_0", connection: "source_net_0", from: "pcb_port_557", to: "pcb_port_563", width: 0.5, net: "GND" },
]

const distToSegment = (p, a, b) => {
  const dx = b.x - a.x
  const dy = b.y - a.y
  const den = dx * dx + dy * dy
  const t = den === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / den))
  return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy))
}

class Heap {
  data = []
  push(item) {
    this.data.push(item)
    for (let i = this.data.length - 1; i > 0;) {
      const p = (i - 1) >> 1
      if (this.data[p].f <= item.f) break
      this.data[i] = this.data[p]
      i = p
      this.data[i] = item
    }
  }
  pop() {
    const root = this.data[0]
    const last = this.data.pop()
    if (this.data.length && last) {
      this.data[0] = last
      for (let i = 0;;) {
        let c = i * 2 + 1
        if (c >= this.data.length) break
        if (c + 1 < this.data.length && this.data[c + 1].f < this.data[c].f) c++
        if (this.data[c].f >= this.data[i].f) break
        ;[this.data[c], this.data[i]] = [this.data[i], this.data[c]]
        i = c
      }
    }
    return root
  }
}

const routed = []
const key = (x, y, layer) => `${Math.round(x / step)},${Math.round(y / step)},${layer}`
const pointForKey = (k) => {
  const [ix, iy, layer] = k.split(",")
  return { x: Number(ix) * step, y: Number(iy) * step, layer }
}

function blocked(point, plan, forVia = false) {
  if (point.x < -64 || point.x > 64 || point.y < -32 || point.y > 32) return true
  const routeRadius = forVia ? 0.225 : plan.width / 2
  const clearance = 0.105
  const endpointIds = new Set([plan.from, plan.to])
  for (const pad of pads) {
    if (!forVia && endpointIds.has(pad.pcb_port_id)) continue
    const padLayers = pad.layer ? [pad.layer] : (pad.layers ?? layers)
    if (!forVia && !padLayers.includes(point.layer)) continue
    const w = pad.width ?? pad.outer_width ?? pad.outer_diameter ?? pad.diameter ?? 0
    const h = pad.height ?? pad.outer_height ?? pad.outer_diameter ?? pad.diameter ?? w
    if (Math.abs(point.x - pad.x) <= w / 2 + routeRadius + clearance && Math.abs(point.y - pad.y) <= h / 2 + routeRadius + clearance) return true
  }
  for (const hole of holes) {
    const radius = (hole.hole_diameter ?? hole.diameter ?? 0) / 2
    if (Math.hypot(point.x - hole.x, point.y - hole.y) <= radius + routeRadius + clearance) return true
  }
  for (const via of vias) {
    if (Math.hypot(point.x - via.x, point.y - via.y) <= (via.outer_diameter ?? 0.45) / 2 + routeRadius + clearance) return true
  }
  for (const trace of oldTraces) {
    if ((trace.connectsTo ?? []).some((id) => endpointIds.has(id))) continue
    if (plan.net === "V3V3" && (trace.pcb_trace_id ?? "").includes("source_net_6_mst")) continue
    const route = trace.route ?? []
    for (let i = 1; i < route.length; i++) {
      const a = route[i - 1]
      const b = route[i]
      if (a.route_type !== "wire" || b.route_type !== "wire") continue
      if (!forVia && a.layer !== point.layer) continue
      if (forVia || a.layer === point.layer) {
        const oldRadius = Math.max(a.width ?? 0.12, b.width ?? 0.12) / 2
        if (distToSegment(point, a, b) <= oldRadius + routeRadius + clearance) return true
      }
    }
  }
  for (const trace of routed) {
    if (trace.net === plan.net && !forVia) continue
    for (let i = 1; i < trace.points.length; i++) {
      const a = trace.points[i - 1]
      const b = trace.points[i]
      if (a.layer !== b.layer) {
        if (Math.hypot(point.x - a.x, point.y - a.y) <= 0.225 + routeRadius + clearance) return true
      } else if (trace.net !== plan.net && (forVia || a.layer === point.layer) && distToSegment(point, a, b) <= trace.width / 2 + routeRadius + clearance) return true
    }
  }
  return false
}

function findPath(plan) {
  const startPort = ports.get(plan.from)
  const endPort = ports.get(plan.to)
  if (!startPort || !endPort) throw new Error(`Missing endpoint for ${plan.id}`)
  const startLayer = startPort.layers?.[0] ?? startPort.layer ?? "top"
  const endLayer = endPort.layers?.[0] ?? endPort.layer ?? "top"
  const start = { x: Math.round(startPort.x / step) * step, y: Math.round(startPort.y / step) * step, layer: startLayer }
  const end = { x: Math.round(endPort.x / step) * step, y: Math.round(endPort.y / step) * step, layer: endLayer }
  const startKey = key(start.x, start.y, start.layer)
  const endKey = key(end.x, end.y, end.layer)
  const heap = new Heap()
  const scores = new Map([[startKey, 0]])
  const parents = new Map()
  heap.push({ key: startKey, f: 0 })
  let visits = 0
  while (heap.data.length) {
    const currentEntry = heap.pop()
    const current = pointForKey(currentEntry.key)
    const g = scores.get(currentEntry.key)
    if (currentEntry.key === endKey) {
      const path = []
      let cursor = endKey
      while (cursor) {
        path.push(pointForKey(cursor))
        cursor = parents.get(cursor)
      }
      path.reverse()
      path[0] = { x: startPort.x, y: startPort.y, layer: startLayer }
      path[path.length - 1] = { x: endPort.x, y: endPort.y, layer: endLayer }
      return path
    }
    if (++visits > 1_500_000) throw new Error(`Search limit for ${plan.id}`)
    const neighbors = [
      { x: current.x + step, y: current.y, layer: current.layer, cost: step },
      { x: current.x - step, y: current.y, layer: current.layer, cost: step },
      { x: current.x, y: current.y + step, layer: current.layer, cost: step },
      { x: current.x, y: current.y - step, layer: current.layer, cost: step },
      ...layers.filter((layer) => layer !== current.layer).map((layer) => ({ x: current.x, y: current.y, layer, cost: 2.5, via: true })),
    ]
    for (const neighbor of neighbors) {
      const neighborKey = key(neighbor.x, neighbor.y, neighbor.layer)
      if (neighborKey !== endKey && blocked(neighbor, plan, neighbor.via)) continue
      if (neighbor.via && blocked(current, plan, true)) continue
      const tentative = g + neighbor.cost
      if (tentative >= (scores.get(neighborKey) ?? Infinity)) continue
      scores.set(neighborKey, tentative)
      parents.set(neighborKey, currentEntry.key)
      const h = Math.abs(neighbor.x - end.x) + Math.abs(neighbor.y - end.y) + (neighbor.layer === end.layer ? 0 : 2)
      heap.push({ key: neighborKey, f: tentative + h })
    }
  }
  throw new Error(`No path for ${plan.id}`)
}

function simplify(points) {
  const out = []
  for (const point of points) {
    const a = out.at(-2)
    const b = out.at(-1)
    if (a && b && a.layer === b.layer && b.layer === point.layer && ((a.x === b.x && b.x === point.x) || (a.y === b.y && b.y === point.y))) out[out.length - 1] = point
    else out.push(point)
  }
  return out
}

const output = []
for (const plan of plans) {
  const points = simplify(findPath(plan))
  routed.push({ ...plan, points })
  const route = []
  for (let i = 0; i < points.length; i++) {
    const point = points[i]
    const prior = points[i - 1]
    if (prior && prior.layer !== point.layer) {
      route.push({ route_type: "via", x: point.x, y: point.y, from_layer: prior.layer, to_layer: point.layer, via_diameter: 0.45, via_hole_diameter: 0.3 })
    }
    route.push({ route_type: "wire", x: point.x, y: point.y, width: plan.width, layer: point.layer, ...(i === 0 ? { start_pcb_port_id: plan.from } : {}), ...(i === points.length - 1 ? { end_pcb_port_id: plan.to } : {}) })
  }
  output.push({ type: "pcb_trace", pcb_trace_id: plan.id, connection_name: plan.connection, connectsTo: [plan.from, plan.to], route })
  console.log(`${plan.id}: ${points.length} points`)
}
fs.writeFileSync("/tmp/backlight-routes.json", `${JSON.stringify(output, null, 2)}\n`)
console.log("Wrote /tmp/backlight-routes.json")