rushabhcodes/ESP32-E-Reader

A compact ESP32-C3 e-reader PCB integrating LiPo charging/protection, USB-C, 3.3 V regulation, e-paper display bias generation, microSD storage, and tactile controls.

Version
1.0.85
License
unset
Stars
3

scripts/refresh-mounting-route-baseline.mjs

import assert from "node:assert/strict";
import { readFile, writeFile } from "node:fs/promises";
import {
	getSimpleRouteJsonFromCircuitJson,
	unrouteCircuitJson,
} from "tscircuit";
import { getVerifiedPcbRoutes } from "../verified-pcb-routes.ts";
import { routingIdentity } from "../routing-identity.ts";
import mounts from "../assets/enclosure/pcb-mounts.json";
import { checkPcbMounts } from "./check-pcb-mounts.mjs";

// A controlled baseline refresh for added mounting holes outside existing
// copper. The production guard stays strict. Existing geometry, connectivity,
// and every routing rule must match; a full build and shorts check follow.
const [beforePath, placementPath] = process.argv.slice(2);
assert(
	beforePath && placementPath,
	"Supply the previous circuit and new unrouted placement JSON",
);
const before = JSON.parse(await readFile(beforePath, "utf8"));
const placement = JSON.parse(await readFile(placementPath, "utf8"));
const baselinePath = new URL("../verified-pcb-routes.json", import.meta.url);
const baseline = JSON.parse(await readFile(baselinePath, "utf8"));
const inputFor = (circuit) => {
	const input = getSimpleRouteJsonFromCircuitJson({
		circuitJson: unrouteCircuitJson(circuit).filter(
			(e) => e.type !== "pcb_via",
		),
		subcircuit_id: "subcircuit_source_group_0",
		minTraceWidth: 0.15,
		nominalTraceWidth: 0.15,
		ignoreExistingTopLevelPcbRouteState: true,
	}).simpleRouteJson;
	input.allowViaInPad = false;
	if (input.traces?.length === 0) delete input.traces;
	return input;
};
assert.equal(
	routingIdentity(inputFor(before)).signature ===
		routingIdentity(baseline.input).signature,
	true,
	"Previous PCB does not match the verified baseline",
);
const input = inputFor(placement);
assert.equal(
	getVerifiedPcbRoutes(input),
	undefined,
	"Physical additions must invalidate the old production baseline",
);
const addedMounts = mounts.mounts.filter(
	(mount) =>
		!before.some(
			(e) =>
				e.type === "pcb_hole" &&
				Math.abs(e.x - mount.x) < 1e-6 &&
				Math.abs(e.y - mount.y) < 1e-6,
		),
);
assert.equal(
	addedMounts.length,
	2,
	"Expected exactly two added mounting holes",
);
const addedObstacles = input.obstacles.filter((obstacle) =>
	addedMounts.some(
		(mount) =>
			Math.abs(obstacle.center.x - mount.x) < 1e-6 &&
			Math.abs(obstacle.center.y - mount.y) < 1e-6,
	),
);
assert.equal(
	addedObstacles.length,
	4,
	"Expected two drills and two circular keepouts",
);
for (const mount of addedMounts) {
	const obstacles = addedObstacles.filter(
		(o) => Math.abs(o.center.x - mount.x) < 1e-6,
	);
	assert(
		obstacles.some(
			(o) =>
				o.isNonPlatedHole &&
				o.shape === "circle" &&
				o.width === mounts.holeDiameter &&
				o.height === mounts.holeDiameter,
		),
	);
	assert(
		obstacles.some(
			(o) =>
				o.type === "oval" &&
				o.width === mounts.copperKeepoutRadius * 2 &&
				o.height === mounts.copperKeepoutRadius * 2,
		),
	);
	for (const obstacle of obstacles) {
		assert.deepEqual([...obstacle.layers].sort(), [
			"bottom",
			"inner1",
			"inner2",
			"top",
		]);
		assert.deepEqual(obstacle.connectedTo, []);
	}
}
// Removing only these four additions must recover the complete previous
// routing input. This also remaps generated pad/hole/port IDs safely.
// Core's search bounds enclose obstacles and the outline with a 1 mm margin.
// Added keepouts can extend that search region without changing the PCB
// outline. Admit only the bounds derived from these four additions.
const expectedBounds = { ...baseline.input.bounds };
for (const obstacle of addedObstacles) {
	expectedBounds.minX = Math.min(
		expectedBounds.minX,
		obstacle.center.x - obstacle.width / 2 - 1,
	);
	expectedBounds.maxX = Math.max(
		expectedBounds.maxX,
		obstacle.center.x + obstacle.width / 2 + 1,
	);
	expectedBounds.minY = Math.min(
		expectedBounds.minY,
		obstacle.center.y - obstacle.height / 2 - 1,
	);
	expectedBounds.maxY = Math.max(
		expectedBounds.maxY,
		obstacle.center.y + obstacle.height / 2 + 1,
	);
}
assert.deepEqual(
	input.bounds,
	expectedBounds,
	"Unexpected routing search bounds change",
);
const previousGeometry = {
	...input,
	bounds: baseline.input.bounds,
	obstacles: input.obstacles.filter((o) => !addedObstacles.includes(o)),
};
const traces = getVerifiedPcbRoutes(previousGeometry);
assert(traces, "Existing obstacles, rules, or electrical connections changed");
const clearance = checkPcbMounts([
	// The routing-disabled build's provisional pours are regenerated after
	// restoring traces. Check the finished pours in the full build instead.
	...placement.filter((e) => e.type !== "pcb_copper_pour"),
	...traces,
	...before.filter((e) => e.type === "pcb_via"),
]);
await writeFile(baselinePath, JSON.stringify({ input, traces }));
console.log(
	JSON.stringify(
		{
			addedMounts: addedMounts.map((m) => m.name),
			unchangedExistingRoutingInput: true,
			preservedTraces: traces.length,
			clearance,
		},
		null,
		2,
	),
);