ShiboSoftwareDev/muse-sensecap-indicator-replacement
A four-layer ESP32-S3 carrier board providing USB-C 5V input, AMS1117 3.3V regulation, reset/boot/user buttons, status LED, test points, and a 24-pin FFC peripheral interface.
- Version
- 0.0.1
- License
- unset
- Stars
- 0
src/jlc-autorouter.ts
import {
SOLVERS,
type AutorouterCompleteEvent,
type AutorouterErrorEvent,
type AutorouterProgressEvent,
type GenericLocalAutorouter,
type SimpleRouteJson,
type SimplifiedPcbTrace,
} from "tscircuit"
type EventHandlers = {
complete: Array<(event: AutorouterCompleteEvent) => void>
error: Array<(event: AutorouterErrorEvent) => void>
progress: Array<(event: AutorouterProgressEvent) => void>
}
/**
* Run tscircuit's multilayer router and normalize every generated through-via
* to the fabrication requirement: 0.30 mm finished drill, 0.45 mm land.
*/
const interfaceFanoutPositions = Array.from({ length: 10 }, (_, index) => ({
x: index % 2 === 0 ? -12.2 : -11.0,
y: 4.75 - index * 0.5,
}))
function normalizeVias(traces: SimplifiedPcbTrace[]): SimplifiedPcbTrace[] {
return traces.map((trace) => ({
...trace,
route: trace.route
// A plated fanout hole already spans all four copper layers. Some
// router solutions add a second via almost concentrically; remove that
// redundant transition before DRC evaluates the route.
.filter(
(point) =>
point.route_type !== "via" ||
!interfaceFanoutPositions.some(
({ x, y }) => Math.hypot(point.x - x, point.y - y) < 0.4,
),
)
.map((point) =>
point.route_type === "via"
? {
...point,
via_diameter: 0.45,
via_hole_diameter: 0.3,
}
: point,
),
}))
}
class JlcFourLayerAutorouter implements GenericLocalAutorouter {
readonly input: SimpleRouteJson
isRouting = false
private timer: ReturnType<typeof setTimeout> | undefined
private cycleCount = 0
private readonly solver: any
private readonly handlers: EventHandlers = {
complete: [],
error: [],
progress: [],
}
constructor(input: SimpleRouteJson) {
this.input = input
this.solver = new SOLVERS.AutoroutingPipelineSolver7_MultiGraph(
input as any,
{},
)
}
private emitError(caught: unknown) {
const error = caught instanceof Error ? caught : new Error(String(caught))
for (const handler of this.handlers.error) {
handler({ type: "error", error })
}
}
private async runCycle() {
if (!this.isRouting) return
try {
if (this.solver.failed) {
this.isRouting = false
this.emitError(this.solver.error ?? "Routing failed")
return
}
if (this.solver.solved) {
this.isRouting = false
const traces = normalizeVias(
this.solver.getOutputSimpleRouteJson().traces ?? [],
)
for (const handler of this.handlers.complete) {
handler({ type: "complete", traces })
}
return
}
const startedAt = Date.now()
const startIterations = this.solver.iterations
while (
Date.now() - startedAt < 250 &&
!this.solver.failed &&
!this.solver.solved
) {
if (typeof this.solver.stepAsync === "function") {
await this.solver.stepAsync()
} else {
this.solver.step()
}
}
this.cycleCount += 1
const elapsed = Math.max(Date.now() - startedAt, 1)
for (const handler of this.handlers.progress) {
handler({
type: "progress",
steps: this.cycleCount,
progress: this.solver.progress,
phase: this.solver.getCurrentPhase(),
iterationsPerSecond:
((this.solver.iterations - startIterations) / elapsed) * 1000,
debugGraphics: this.solver.preview?.(),
})
}
this.timer = setTimeout(() => void this.runCycle(), 0)
} catch (error) {
this.isRouting = false
this.emitError(error)
}
}
start() {
if (this.isRouting) return
this.isRouting = true
void this.runCycle()
}
stop() {
this.isRouting = false
this.solver.stop?.()
if (this.timer) clearTimeout(this.timer)
this.timer = undefined
}
on(event: "complete", callback: (event: AutorouterCompleteEvent) => void): void
on(event: "error", callback: (event: AutorouterErrorEvent) => void): void
on(event: "progress", callback: (event: AutorouterProgressEvent) => void): void
on(
event: keyof EventHandlers,
callback:
| ((event: AutorouterCompleteEvent) => void)
| ((event: AutorouterErrorEvent) => void)
| ((event: AutorouterProgressEvent) => void),
) {
;(this.handlers[event] as Array<(event: never) => void>).push(
callback as (event: never) => void,
)
}
solveSync() {
this.solver.solve()
if (this.solver.failed) {
throw new Error(this.solver.error ?? "Routing failed")
}
return normalizeVias(this.solver.getOutputSimpleRouteJson().traces ?? [])
}
}
export const createJlcFourLayerAutorouter = async (
input: SimpleRouteJson,
): Promise<GenericLocalAutorouter> => new JlcFourLayerAutorouter(input)