abse/gameboy-advance

This code defines a collection of hardware components, including microcontrollers, sensors, display modules, power management ICs, and passive/passive components, each modeled as 3D symbols with specific footprints, pin configurations, and associated models for PCB and schematic design.

Version
0.0.10
License
unset
Stars
0

checks/report-drc.ts

import { mkdirSync, readFileSync, writeFileSync } from "node:fs"
import { dirname } from "node:path"

type Element = Record<string, unknown>

const input = process.argv[2] ?? "dist/index/circuit.json"
const output = process.argv[3] ?? "artifacts/drc-report.json"
const elements = JSON.parse(readFileSync(input, "utf8")) as Element[]
const byId = new Map<string, Element>()
for (const element of elements) {
  for (const [key, value] of Object.entries(element)) {
    if (key.endsWith("_id") && typeof value === "string") byId.set(value, element)
  }
}

const componentNameByPcbId = new Map<string, string>()
for (const component of elements.filter((e) => e.type === "pcb_component")) {
  const pcbId = component.pcb_component_id
  const sourceId = component.source_component_id
  const source = typeof sourceId === "string" ? byId.get(sourceId) : undefined
  if (typeof pcbId === "string") {
    componentNameByPcbId.set(pcbId, String(source?.name ?? pcbId))
  }
}

function collectIds(value: unknown, ids = new Set<string>()): Set<string> {
  if (Array.isArray(value)) {
    for (const item of value) collectIds(item, ids)
  } else if (value && typeof value === "object") {
    for (const [key, item] of Object.entries(value as Element)) {
      if (key.endsWith("_id") && typeof item === "string") ids.add(item)
      collectIds(item, ids)
    }
  }
  return ids
}

function findCoordinate(value: unknown): { x?: number; y?: number } {
  if (!value || typeof value !== "object") return {}
  const record = value as Element
  if (typeof record.pcb_x === "number" && typeof record.pcb_y === "number") {
    return { x: record.pcb_x, y: record.pcb_y }
  }
  for (const child of Object.values(record)) {
    const point = findCoordinate(child)
    if (point.x !== undefined) return point
  }
  return {}
}

function describe(id: string) {
  if (componentNameByPcbId.has(id)) return componentNameByPcbId.get(id)!
  const element = byId.get(id)
  if (!element) return id
  return String(
    element.name ??
      element.source_trace_id ??
      element.pcb_trace_id ??
      element.source_port_id ??
      id,
  )
}

const errors = elements.filter((element) =>
  typeof element.type === "string" && element.type.endsWith("_error"),
)
const records = errors.map((error) => {
  const related = [...collectIds(error)].filter((id) => id !== error.pcb_error_id)
  const point = findCoordinate(error)
  const relatedElements = related.map(describe).sort()
  const signature = [
    error.type,
    relatedElements.join(","),
    point.x?.toFixed(2) ?? "",
    point.y?.toFixed(2) ?? "",
  ].join("|")
  return {
    type: error.type,
    message: error.message ?? null,
    locationMm: point.x === undefined ? null : point,
    relatedElements,
    signature,
  }
})

const byType = Object.groupBy(records, (record) => String(record.type))
const report = {
  input,
  errorCount: records.length,
  byType: Object.fromEntries(
    Object.entries(byType).map(([type, group]) => [type, group?.length ?? 0]),
  ),
  errors: records,
  attribution: {
    note: "A record identifies the DRC type, location when emitted, and related PCB/source elements. To determine whether it was introduced by a change, compare these stable signatures against an equivalent baseline routing run using the same router version and board input.",
  },
}

mkdirSync(dirname(output), { recursive: true })
writeFileSync(output, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))