astra/f1c100s

The code defines the physical pin layout, labels, and footprint for the F1C100S system-on-chip (SoC) component used in electronic devices.

Version
0.9.2
License
unset
Stars
0

src/saved-paths.tsx

import type { FanoutTracePath } from "@tscircuit/props";
import type {
	GenericLocalAutorouter,
	SimpleRouteJson,
	Subcircuit,
} from "tscircuit";
import type { LayoutProfile } from "./profiles";
import native from "./generated/native.trace-paths.json";
import lcdTopStorageRight from "./generated/lcd_top_storage_right.trace-paths.json";
import lcdRightStorageBottom from "./generated/lcd_right_storage_bottom.trace-paths.json";
import lcdTopStorageBottom from "./generated/lcd_top_storage_bottom.trace-paths.json";
import lcdRightStorageLeft from "./generated/lcd_right_storage_left.trace-paths.json";
const profiles = {
	native,
	lcd_top_storage_right: lcdTopStorageRight,
	lcd_right_storage_bottom: lcdRightStorageBottom,
	lcd_top_storage_bottom: lcdTopStorageBottom,
	lcd_right_storage_left: lcdRightStorageLeft,
};

const rootsWithPlatedLayerAccess = new WeakSet<object>();

/** The pinned core drops pcb_port.layers when making router input. Restore
 * layer access from actual plated-hole obstacles immediately before routing,
 * for both saved fanouts and the carrier's configured local autorouter. */
function preservePlatedLayerAccess(instance: Subcircuit) {
	const root = instance.root!;
	if (rootsWithPlatedLayerAccess.has(root)) return;
	rootsWithPlatedLayerAccess.add(root);
	root.on(
		"autorouting:start",
		({ simpleRouteJson: input }: { simpleRouteJson: SimpleRouteJson }) => {
			if (!input) return;
			for (const connection of input.connections)
				for (const point of connection.pointsToConnect) {
					const hole = input.obstacles.find(
						(o) =>
							o.circuitJsonMetadata?.pcb_plated_hole_id &&
							Math.hypot(o.center.x - point.x, o.center.y - point.y) < 1e-5 &&
							(o.circuitJsonMetadata.pcb_port_id === point.pcb_port_id ||
								o.connectedTo.includes(
									connection.source_trace_id ?? connection.name,
								)),
					);
					if (hole) point.layers = [...hole.layers];
				}
		},
	);
}

/** Every saved path on a net ends at the same junction. This final phase checks
 * that invariant; it never solves or invents copper. Parent routing is separate. */
export async function joinSavedPathExits(
	input: SimpleRouteJson,
): Promise<GenericLocalAutorouter> {
	for (const connection of input.connections) {
		const points = connection.pointsToConnect,
			first = points[0];
		// Core's SRJ currently defaults a physical port to its first layer.
		// A real plated barrel joins coincident exits on different layers.
		const barrel =
			first &&
			input.obstacles.find(
				(o) =>
					o.circuitJsonMetadata?.pcb_plated_hole_id &&
					Math.hypot(o.center.x - first.x, o.center.y - first.y) < 1e-4 &&
					o.connectedTo.some(
						(id) =>
							id === connection.name ||
							id === connection.source_trace_id ||
							points.some((p) => p.pcb_port_id === id),
					) &&
					points.every((p) =>
						(p.layers ?? [p.layer]).some((l) => o.layers.includes(l)),
					),
			);
		if (
			first &&
			points.some(
				(p) =>
					Math.hypot(p.x - first.x, p.y - first.y) > 1e-4 ||
					(!barrel &&
						!(p.layers ?? [p.layer]).some((l) =>
							(first.layers ?? [first.layer]).includes(l),
						)),
			)
		)
			throw new Error(`Saved F1C100S paths do not meet for ${connection.name}`);
	}
	const listeners: Record<string, ((event: any) => void)[]> = {
		complete: [],
		error: [],
		progress: [],
	};
	return {
		input,
		isRouting: false,
		on(event, listener) {
			listeners[event]!.push(listener);
		},
		start() {
			queueMicrotask(() =>
				listeners.complete!.forEach((f) => f({ type: "complete", traces: [] })),
			);
		},
		stop() {},
		solveSync() {
			return [];
		},
	};
}

/** Use the native saved-fanout API for copper, retaining the imported footprint
 * and schematic symbols. Electrical traces remain outside the fanout. */
export function attachSavedPaths(instance: Subcircuit, profile: LayoutProfile) {
	preservePlatedLayerAccess(instance);
	(instance as any)._isInflatedFromCircuitJson = false;
	const check = instance.doInitialPcbDesignRuleChecks.bind(instance);
	instance.doInitialPcbDesignRuleChecks = () => {
		deduplicateSharedVias(instance);
		check();
	};
	const module = instance.selectOne(".MODULE") as any;
	const components = [...module.children];
	module.add(
		<fanout
			name="COPPER"
			pcbX={0}
			pcbY={0}
			schLayout={{ layoutMode: "relative" }}
			pcbTracePaths={structuredClone(profiles[profile]) as FanoutTracePath[]}
		/>,
	);
	const fanout = module.selectOne(".COPPER");
	fanout._doInitialSchematicLayoutSections = () => {};
	for (const component of components) {
		module.children = module.children.filter((c: any) => c !== component);
		fanout.add(component);
	}
	for (const trace of instance.selectAll("trace") as any[]) {
		for (const props of [trace.props, trace._parsedProps]) {
			delete props.pcbPath;
			delete props.pcbStraightLine;
			props.maxLength = /^N_HOSC[IO]$/.test(props.name ?? "") ? 10 : 1000;
			if (/^N_HOSC[IO]$/.test(props.name ?? "")) props.maxViaCount = 0;
			props.path = props.path?.map((p: string) =>
				p.replace(".MODULE > ", ".MODULE .COPPER > "),
			);
		}
	}
}

/** Core currently emits a via for every path that shares it. Keep one physical
 * drill per same-net location before either native or independent DRC runs. */
function deduplicateSharedVias(instance: Subcircuit) {
	const db = instance.root!.db;
	const seen = new Map<string, string>();
	for (const via of db.pcb_via.list()) {
		if (via.subcircuit_id !== instance.subcircuit_id) continue;
		const key = [
			via.x.toFixed(6),
			via.y.toFixed(6),
			via.hole_diameter,
			via.outer_diameter,
			[...via.layers].sort().join(","),
		].join(":");
		const net = db.pcb_trace.get(via.pcb_trace_id!)?.source_trace_id ?? "";
		if (seen.has(key)) {
			if (seen.get(key) !== net)
				throw new Error("Different nets share an F1C100S via");
			db.pcb_via.delete(via.pcb_via_id);
		} else seen.set(key, net);
	}
}