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

pre-expansion-autorouter.ts

import {
	type AutorouterCompleteEvent,
	type AutorouterErrorEvent,
	type AutorouterProgressEvent,
	type GenericLocalAutorouter,
	type SimpleRouteJson,
	type SimplifiedPcbTrace,
	SOLVERS,
} from "tscircuit";
import { getVerifiedPcbRoutes } from "./verified-pcb-routes";
import { removeSupersededRoutingSeeds } from "./routing-output";

type EventHandlers = {
	complete: Array<(event: AutorouterCompleteEvent) => void>;
	error: Array<(event: AutorouterErrorEvent) => void>;
	progress: Array<(event: AutorouterProgressEvent) => void>;
};

/**
 * Runs the standard local routing pipeline through trace-width and DRC repair,
 * then returns its traces before the optional power-trace expansion pass.
 * Explicit net widths and copper pours still define the board's power copper.
 */
export class PreExpansionAutorouter implements GenericLocalAutorouter {
	readonly input: SimpleRouteJson;
	isRouting = false;

	private readonly solver;
	private readonly eventHandlers: EventHandlers = {
		complete: [],
		error: [],
		progress: [],
	};
	private timeoutId?: ReturnType<typeof setTimeout>;
	private cycleCount = 0;

	constructor(
		input: SimpleRouteJson,
		private readonly verifiedTraces?: SimplifiedPcbTrace[],
	) {
		this.input = input;
		this.solver = new SOLVERS.AutoroutingPipelineSolver9_PreloadedTraceGraph(
			input as ConstructorParameters<
				typeof SOLVERS.AutoroutingPipelineSolver9_PreloadedTraceGraph
			>[0],
		);
	}

	start() {
		if (this.isRouting) return;
		this.isRouting = true;
		this.runCycle();
	}

	stop() {
		this.isRouting = false;
		if (this.timeoutId !== undefined) clearTimeout(this.timeoutId);
		this.timeoutId = undefined;
	}

	on(
		event: "complete" | "error" | "progress",
		callback:
			| ((event: AutorouterCompleteEvent) => void)
			| ((event: AutorouterErrorEvent) => void)
			| ((event: AutorouterProgressEvent) => void),
	) {
		(this.eventHandlers[event] as Array<typeof callback>).push(callback);
	}

	solveSync(): SimplifiedPcbTrace[] {
		if (this.verifiedTraces) return this.verifiedTraces;
		this.solver.solveUntilPhase("powerTraceExpansionSolver");
		if (this.solver.failed) {
			throw new Error(this.solver.error || "Routing failed");
		}
		return this.getTraces();
	}

	private getTraces(): SimplifiedPcbTrace[] {
		if (this.verifiedTraces) return this.verifiedTraces;
		return removeSupersededRoutingSeeds(this.input, [
			...this.solver.getUpdatedPreloadedTraces(),
			...this.solver.getNewTracesBeforePowerExpansion(),
		]);
	}

	private runCycle = () => {
		if (!this.isRouting) return;

		try {
			if (this.verifiedTraces) {
				this.isRouting = false;
				for (const handler of this.eventHandlers.complete) {
					handler({ type: "complete", traces: this.verifiedTraces });
				}
				return;
			}
			const cycleStartedAt = Date.now();
			const initialIterations = this.solver.iterations;

			while (
				Date.now() - cycleStartedAt < 250 &&
				!this.solver.failed &&
				!this.solver.solved &&
				this.solver.getCurrentPhase() !== "powerTraceExpansionSolver"
			) {
				this.solver.step();
			}

			if (this.solver.failed) {
				throw new Error(this.solver.error || "Routing failed");
			}

			if (
				this.solver.solved ||
				this.solver.getCurrentPhase() === "powerTraceExpansionSolver"
			) {
				this.isRouting = false;
				const event: AutorouterCompleteEvent = {
					type: "complete",
					traces: this.getTraces(),
				};
				for (const handler of this.eventHandlers.complete) handler(event);
				return;
			}

			this.cycleCount += 1;
			const elapsedMs = Math.max(Date.now() - cycleStartedAt, 1);
			const event: AutorouterProgressEvent = {
				type: "progress",
				steps: this.cycleCount,
				progress: this.solver.progress,
				phase: this.solver.getCurrentPhase(),
				iterationsPerSecond:
					((this.solver.iterations - initialIterations) / elapsedMs) * 1000,
			};
			for (const handler of this.eventHandlers.progress) handler(event);

			this.timeoutId = setTimeout(this.runCycle, 0);
		} catch (cause) {
			this.isRouting = false;
			const event: AutorouterErrorEvent = {
				type: "error",
				error: cause instanceof Error ? cause : new Error(String(cause)),
			};
			for (const handler of this.eventHandlers.error) handler(event);
		}
	};
}

export const createPreExpansionAutorouter = async (input: SimpleRouteJson) =>
	new PreExpansionAutorouter(input, getVerifiedPcbRoutes(input));