gokul/fieldkit-usb

USB-C-powered ESP32-S3 Wi-Fi/Bluetooth handheld with an SMD 128x32 OLED, six navigation buttons, USB-C power/data, complete checked routing, and four native schematic sheets.

Version
0.4.2
License
UNLICENSED
Stars
0

routing/reviewed-router.ts

import type { SimpleRouteJson, SimpleRouteConnection, SimplifiedPcbTrace } from "@tscircuit/capacity-autorouter"
import reviewedRoute from "./reviewed-route.json"

// Official local autorouter output, with the documented plane connections and clearance
// repairs. Refuse changed geometry instead of silently reusing stale copper.
const saved: SimpleRouteJson = reviewedRoute as SimpleRouteJson
type PointKey = string
type ConnectionKey = string
type RoutingId = string
type CompleteEvent = { type: "complete"; traces: SimplifiedPcbTrace[] }
// Browser and native math libraries can differ by a few floating-point bits
// while computing the same rounded board outline. Compare geometry at 1e-9 mm
// and normalize property order; real layout and rule changes still fail closed.
const geometryKey = (geometry: unknown): string => JSON.stringify(geometry, (_key, field: unknown) => {
  if (typeof field === "number") return Number(field.toFixed(9))
  if (field && typeof field === "object" && !Array.isArray(field)) {
    return Object.fromEntries(Object.entries(field).sort(([firstKey], [secondKey]) => firstKey.localeCompare(secondKey)))
  }
  return field
})
const pointKey = (point: SimpleRouteConnection["pointsToConnect"][number]): PointKey =>
  JSON.stringify([point.x.toFixed(6), point.y.toFixed(6), point.layer ?? point.layers])
const connectionKey = (connection: SimpleRouteConnection): ConnectionKey =>
  JSON.stringify([connection.nominalTraceWidth, connection.pointsToConnect.map(pointKey).sort()])
const obstacleKey = (obstacle: SimpleRouteJson["obstacles"][number]) => {
  const { connectedTo, obstacleId, componentId, circuitJsonMetadata, offBoardConnectsTo, zLayers, __zLayers, ...geometry } = obstacle as SimpleRouteJson["obstacles"][number] & { zLayers?: number[]; __zLayers?: number[] }
  return geometryKey(geometry)
}
const boardKey = (route: SimpleRouteJson) => {
  const { connections, obstacles, traces, ...board } = route
  return geometryKey(board)
}

export const reviewedRouter = async (input: SimpleRouteJson) => {
  if (boardKey(input) !== boardKey(saved)
    || JSON.stringify(input.obstacles.map(obstacleKey).sort()) !== JSON.stringify(saved.obstacles.map(obstacleKey).sort())
    || input.connections.length !== saved.connections.length) {
    throw new Error("Board geometry or routing rules changed. Regenerate and review the official local autoroute before updating routing/reviewed-route.json.")
  }
  const currentConnections = new Map<ConnectionKey, SimpleRouteConnection>(input.connections.map(connection => [connectionKey(connection), connection]))
  const replacements = new Map<RoutingId, RoutingId>()
  for (const connection of saved.connections) {
    const current = currentConnections.get(connectionKey(connection))
    if (!current) throw new Error(`Board net topology changed: ${connection.name}`)
    replacements.set(connection.name, current.name)
    const currentPoints = new Map<PointKey, SimpleRouteConnection["pointsToConnect"][number]>(current.pointsToConnect.map(point => [pointKey(point), point]))
    for (const point of connection.pointsToConnect) {
      const currentPoint = currentPoints.get(pointKey(point))!
      if (point.pcb_port_id && currentPoint.pcb_port_id) replacements.set(point.pcb_port_id, currentPoint.pcb_port_id)
    }
  }
  const traces: SimplifiedPcbTrace[] = (saved.traces ?? []).map(trace => ({
    ...trace,
    route: structuredClone(trace.route),
    connection_name: replacements.get(trace.connection_name)!,
    connectsTo: trace.connectsTo?.map(endpoint => endpoint.replace(/source_net_\d+|pcb_port_\d+/g, identifier => replacements.get(identifier) ?? identifier)),
  }))
  const completeHandlers: ((event: CompleteEvent) => void)[] = []
  return {
    on: (eventName: string, handler: (event: CompleteEvent) => void) => {
      if (eventName === "complete") completeHandlers.push(handler)
    },
    start: () => { for (const handler of completeHandlers) handler({ type: "complete", traces }) },
    stop: () => {},
    getOutputSimpleRouteJson: () => ({ ...input, traces }),
  }
}