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 }),
}
}