astra/f1c100s

This code defines and renders a four-layer, top-mounted F1C100S module with detailed pin assignments, schematic annotations, and layout profiles, facilitating design, validation, and routing of the hardware including support for decoupling capacitors, crystal oscillators, and external connectors.

Version
0.11.0
License
unset
Stars
0

scripts/generate.ts

import { checkDecouplingTargets } from "./check-decoupling-targets";
import { checkCapacitorOrientation } from "./check-capacitor-orientation";
import { getSimpleRouteJsonFromCircuitJson } from "tscircuit";
import { createHash } from "node:crypto";
import { mkdir } from "node:fs/promises";
import {
	LAYOUT_PROFILES,
	assertLayoutProfile,
	RULES,
	MODULE_SIZE,
	type LayoutProfile,
} from "../src/profiles";
import { routeGrid } from "./grid-router";
import { renderProfile } from "./render";
import { validateCircuit, circuitMetrics } from "./validate";
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg";

const chosen = process.argv[2];
if (chosen) assertLayoutProfile(chosen);
const profiles: LayoutProfile[] = chosen
	? [chosen as LayoutProfile]
	: [...LAYOUT_PROFILES];
const workDir = process.env.F1C100S_WORK_DIR ?? ".cache/f1c100s";
await mkdir("src/generated", { recursive: true });
await mkdir("previews", { recursive: true });
await mkdir(workDir, { recursive: true });
for (const profile of profiles) {
	console.log(`GENERATING ${profile}`);
	const { json: placed } = await renderProfile(profile);
	const placementErrors = placed.filter(
		(e: any) =>
			e.type === "pcb_footprint_overlap_error" ||
			e.type === "pcb_placement_error" ||
			e.type === "pcb_courtyard_overlap_error",
	);
	if (placementErrors.length) throw new Error(JSON.stringify(placementErrors));
	const { simpleRouteJson: input } = getSimpleRouteJsonFromCircuitJson({
		circuitJson: placed,
		minTraceWidth: 0.12,
		nominalTraceWidth: 0.12,
		minTraceToPadEdgeClearance: 0.1,
		minViaPadDiameter: 0.45,
		minViaHoleDiameter: 0.2,
	});
	const hash = createHash("sha256").update(JSON.stringify(input)).digest("hex");
	const components = placed.filter(
		(e: any) => e.type === "source_component",
	) as any[];
	const portId = (name: string, pin: number) => {
		const c = components.find((c) => c.name === name);
		const sp = placed.find(
			(e: any) =>
				e.type === "source_port" &&
				e.source_component_id === c.source_component_id &&
				e.pin_number === pin,
		) as any;
		return (
			placed.find(
				(e: any) =>
					e.type === "pcb_port" && e.source_port_id === sp.source_port_id,
			) as any
		).pcb_port_id as string;
	};
	const requiredPairs: [string, string][] = components
		.filter((c) => /^C_D\d+$/.test(c.name))
		.map((c) => [portId(c.name, 1), portId("U1", Number(c.name.slice(3)))]);

	let first: string[] = [],
		traces;
	for (let attempt = 0; attempt < 20; attempt++) {
		try {
			traces = routeGrid(input, {
				requiredPairs,
				first,
				onProgress: (s) => {
					if (s.startsWith("1/") || s.startsWith("78/")) console.log(s);
				},
			});
			break;
		} catch (e) {
			console.log(`Attempt ${attempt + 1}: ${(e as Error).message}`);
			const connection = (e as any).connection as string | undefined;
			if (!connection || attempt === 19) throw e;
			first = [connection, ...first.filter((c) => c !== connection)];
		}
	}
	if (!traces) throw new Error("No solved routes");
	const { json } = await renderProfile(profile, traces);
	const errors = [
		...(await validateCircuit(json)),
		...checkCapacitorOrientation(json),
		...checkDecouplingTargets(json),
	];
	await Bun.write(
		`${workDir}/${profile}.drc.json`,
		JSON.stringify(errors, null, 2),
	);
	if (errors.length)
		throw new Error(`${profile}: ${errors.length} DRC errors; see ${workDir}`);
	const metrics = circuitMetrics(json);
	// Board elements are not part of the reusable child subcircuit.
	const boardGroup = (json.find((e: any) => e.type === "source_board") as any)
		?.source_group_id;
	const stored = json
		.filter(
			(e: any) =>
				!e.type.endsWith("_error") &&
				!e.type.endsWith("_warning") &&
				e.type !== "pcb_board" &&
				e.type !== "source_board" &&
				!(e.type === "source_group" && e.source_group_id === boardGroup),
		)
		.map((e: any) => {
			const copy = { ...e };
			if (
				copy.type === "source_group" &&
				copy.parent_source_group_id === boardGroup
			)
				delete copy.parent_source_group_id;
			return copy;
		});
	await Bun.write(
		`src/generated/${profile}.circuit.json`,
		JSON.stringify(stored),
	);
	await Bun.write(
		`src/generated/${profile}.metrics.json`,
		JSON.stringify(
			{
				profile,
				inputHash: hash,
				moduleSizeMm: MODULE_SIZE,
				rules: RULES,
				...metrics,
				drcErrors: 0,
			},
			null,
			2,
		) + "\n",
	);
	await Bun.write(
		`previews/${profile}.svg`,
		convertCircuitJsonToPcbSvg(json, { width: 1100, height: 1100 }),
	);
	console.log(`VALIDATED ${profile}`, metrics);
}