shibosoftwaredev/f1c100s-linux-nema17-stepper-controller
A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.
- Version
- 0.4.56
- License
- unset
- Stars
- 0
scripts/check-schematic-review-regression.mjs
import fs from "node:fs"
import assert from "node:assert/strict"
import crypto from "node:crypto"
const baseline = "../../checkpoints/nema17-before-rishabh-review-v053-DuFOGi/restore-verified/dist/index/circuit.json"
const input = "dist/index/circuit.json"
const read = p => JSON.parse(fs.readFileSync(p, "utf8"))
const before = read(baseline), after = read(input)
const sha = p => crypto.createHash("sha256").update(fs.readFileSync(p)).digest("hex")
const physical = c => c.filter(e => (e.type.startsWith("pcb_") && !e.type.endsWith("_warning") && e.type !== "pcb_group") || e.type.startsWith("cad_"))
assert.deepEqual(physical(after), physical(before), "Schematic-only checkpoint must not change physical records")
const sources = new Map(after.filter(e => e.type === "source_trace").map(e => [e.source_trace_id, e]))
const traces = new Map(after.filter(e => e.type === "pcb_trace").map(e => [e.pcb_trace_id, e]))
const parent = new Map()
function root(id) { if (!parent.has(id)) parent.set(id, id); if (parent.get(id) !== id) parent.set(id, root(parent.get(id))); return parent.get(id) }
for (const e of after) {
const ids = e.type === "source_trace" ? [...(e.connected_source_port_ids ?? []), ...(e.connected_source_net_ids ?? [])]
: e.type === "source_component_internal_connection" ? e.source_port_ids ?? [] : []
for (const id of ids.slice(1)) parent.set(root(id), root(ids[0]))
}
const mapping = read("routing/net-map.json").roots
function net(e) {
const s = sources.get(e.source_trace_id ?? traces.get(e.pcb_trace_id)?.source_trace_id)
assert(s, "Every emitted copper object needs an actual source association")
return mapping[root([...(s.connected_source_port_ids ?? []), ...(s.connected_source_net_ids ?? [])][0])]
}
const wires = [], vias = [], seen = new Set()
for (const e of after) {
if (e.type === "pcb_trace" && e.source_trace_id) {
for (let i = 1; i < e.route.length; i++) {
const a = e.route[i-1], b = e.route[i]
if (a.route_type !== "wire" || b.route_type !== "wire" || a.layer !== b.layer || Math.hypot(a.x-b.x,a.y-b.y) < 1e-8) continue
const points = [[a.x,a.y],[b.x,b.y]], width = Math.max(a.width,b.width)
const key = JSON.stringify([net(e),a.layer,width,[...points].sort((a,b) => a[0]-b[0] || a[1]-b[1])])
if (!seen.has(key)) { seen.add(key); wires.push({net:net(e),layer:a.layer,width,points}) }
}
} else if (e.type === "pcb_via") {
assert.equal(e.hole_diameter,.3); assert.equal(e.outer_diameter,.45)
assert.deepEqual(e.layers,["top","inner1","inner2","bottom"])
const key=JSON.stringify([net(e),e.x,e.y])
if (!seen.has(key)) { seen.add(key); vias.push({net:net(e),x:e.x,y:e.y}) }
}
}
const copperBytes = JSON.stringify({wires,vias},null,2)+"\n"
fs.writeFileSync("routing/review-emitted-physical.json", copperBytes)
// Refresh the verification snapshot from actual emitted copper. This replaces
// a historical routing-session file, not any PCB trace or replay input.
fs.writeFileSync("routing/physical-copper.json", copperBytes)
const report={circuitSha256:sha(input),baselineSha256:sha(baseline),physicalRecordsUnchanged:true,
physicalRecordCount:physical(after).length,wires:wires.length,vias:vias.length,
physicalCopperSnapshotSha256:sha("routing/physical-copper.json"),
schematicStyleIssues:read("reports/review-style/analysis.json").issueCount,
capacitorTopRelocationCompleted:false,published:false}
fs.writeFileSync("reports/review-schematic-regression.json",JSON.stringify(report,null,2)+"\n")
console.log(JSON.stringify(report))