krishnax12/watchy-eink-smartwatch
Unfinished prototype (not production-ready): 4 native DRC errors, 2 display-flex intersections, and missing antenna radiator copper remain
- Version
- 1.2.7
- License
- MIT
- Stars
- 1
scripts/check-built.ts
import type { AnyCircuitElement } from "circuit-json";
import expectedNets from "../tests/fixtures/expected-nets.json";
const circuitPath = process.argv[2] ?? "dist/index/circuit.json";
const reportPath = process.argv[3] ?? "reports/routing-validation.json";
const circuit: AnyCircuitElement[] = await Bun.file(circuitPath).json();
const components = new Map(
circuit
.filter((element) => element.type === "source_component")
.map((element) => [element.source_component_id, element.name]),
);
const ports = circuit.filter((element) => element.type === "source_port");
const expectedGroups = Object.values(expectedNets)
.map((members) => [...members].sort().join("|"))
.sort();
const actualGroups = new Map<string, string[]>();
const expectedPins = new Set(Object.values(expectedNets).flat());
for (const port of ports) {
if (!port.source_component_id) continue;
const pin = components.get(port.source_component_id) + "." + port.pin_number;
if (!expectedPins.has(pin)) continue;
const key = port.subcircuit_connectivity_map_key;
if (!key) throw new Error("Missing connectivity key: " + pin);
actualGroups.set(key, [...(actualGroups.get(key) ?? []), pin]);
}
const actualGroupsSorted = [...actualGroups.values()]
.map((members) => members.sort().join("|"))
.sort();
if (JSON.stringify(expectedGroups) !== JSON.stringify(actualGroupsSorted))
throw new Error("Electrical connectivity changed");
const errors = circuit.filter((element) => element.type.endsWith("_error"));
const warnings = circuit.filter((element) => element.type.endsWith("_warning"));
const traces = circuit.filter((element) => element.type === "pcb_trace");
// The built-in DRC does not currently enforce every bus allowed-layer rule.
const topOnlyNetNames = new Set([
"XTAL_40M_OUT",
"XTAL_40M_FB",
"XTAL_40M_IN",
"XTAL_32K_P",
"XTAL_32K_N",
"RF_ANT",
"RF_IN",
]);
const topOnlySourceTraces = new Map(
circuit
.filter((element) => element.type === "source_trace")
.filter((trace) => trace.name && topOnlyNetNames.has(trace.name))
.map((trace) => [trace.source_trace_id, trace.name]),
);
const violatedLayerRules = new Set<string>();
for (const trace of traces) {
const netName = trace.source_trace_id
? topOnlySourceTraces.get(trace.source_trace_id)
: undefined;
if (!netName) continue;
if (
trace.route.some(
(point) =>
point.route_type === "via" ||
(point.route_type === "wire" && point.layer !== "top"),
)
)
violatedLayerRules.add(netName);
}
const routingConstraintErrors = [...violatedLayerRules]
.sort()
.map(
(netName) =>
`${netName} violates the required top-only, no-via routing rule`,
);
const vias = circuit.filter((element) => element.type === "pcb_via");
const report = {
circuitPath,
connectivityPreserved: true,
nets: expectedGroups.length,
connectedPins: expectedPins.size,
traces: traces.length,
vias: vias.length,
routed: traces.length > 0,
errors,
routingConstraintErrors,
warningCount: warnings.length,
};
await Bun.write(reportPath, JSON.stringify(report, null, 2) + "\n");
console.log(
JSON.stringify(
{
...report,
errors: errors.map((error) => ({
type: error.type,
message: "message" in error ? error.message : "Unknown error",
})),
},
null,
2,
),
);
if (errors.length || routingConstraintErrors.length || traces.length === 0)
process.exitCode = 1;