seveibar/f1c1990s-dev-board

This code manages the entire PCB assembly process for a computer hardware module, including component placement, schematic integration, automated routing, copper pouring, and design rule checks, focused on a four-layer carrier board with integrated support components and connectors.

Version
1.8.0
License
unset
Stars
0

modules/f1c100s/src/index.tsx

import type { CircuitJson } from "circuit-json";
import { selectBreakouts } from "./selective-breakouts";
import { joinSavedPathExits } from "./saved-paths";
import { attachSchematicLayout } from "./schematic";
import {
	LAYOUT_PROFILES,
	assertLayoutProfile,
	type LayoutProfile,
} from "./profiles";
import { EXTERNAL_NETS } from "./pin-map";
import native from "./generated/native.circuit.json";
import lcdTopStorageRight from "./generated/lcd_top_storage_right.circuit.json";
import lcdRightStorageBottom from "./generated/lcd_right_storage_bottom.circuit.json";
import lcdTopStorageBottom from "./generated/lcd_top_storage_bottom.circuit.json";
import lcdRightStorageLeft from "./generated/lcd_right_storage_left.circuit.json";

export { LAYOUT_PROFILES, type LayoutProfile };
export {
	F1C100SLcdSchematicBox,
	F1C100SStorageSchematicBox,
	F1C100SGpioSchematicBox,
	F1C100SAudioSchematicBox,
	F1C100SVideoTouchSchematicBox,
	F1C100SSystemSchematicBox,
	F1C100SPowerSchematicBox,
	type F1C100SSchematicBoxProps,
} from "./schematic-boxes";
const profiles: Record<LayoutProfile, unknown> = {
	native,
	lcd_top_storage_right: lcdTopStorageRight,
	lcd_right_storage_bottom: lcdRightStorageBottom,
	lcd_top_storage_bottom: lcdTopStorageBottom,
	lcd_right_storage_left: lcdRightStorageLeft,
};
export interface F1C100SModuleProps {
	name: string;
	layoutProfile?: LayoutProfile;
	pcbX?: number;
	pcbY?: number;
	/** Rotation of the complete stored module, including its exit pads. */
	pcbRotation?: number;
	schX?: number;
	schY?: number;
	schSheetName?: string;
	/** Custom mode lets callers place the exported schematic-box components. */
	schematicLayout?: "default" | "custom";
	/** Only truthy entries create breakout pads and external copper. Internal support is always retained. */
	connections?: Partial<Record<string, string>>;
}

/** A fresh copy prevents one placed instance from mutating another's routes. */
export function getF1C100SCircuitJson(
	layoutProfile: LayoutProfile = "native",
): CircuitJson {
	assertLayoutProfile(layoutProfile);
	return structuredClone(profiles[layoutProfile]) as CircuitJson;
}

/** Inflate the imported components and connectivity only. Copper is loaded
 * separately by the native fanout pcbTracePaths API. */
function prepareForInflation(json: CircuitJson): CircuitJson {
	// Dedicated decouplers have explicit two-port source traces. Their supply
	// connection goes through the assigned U1 pin; only their return joins GND.
	const records = json as any[];
	const processor = records.find(
		(e) => e.type === "source_component" && e.name === "U1",
	);
	for (const cap of records.filter(
		(e) => e.type === "source_component" && /^C_D\d+$/.test(e.name),
	)) {
		const capPort = records.find(
			(e) =>
				e.type === "source_port" &&
				e.source_component_id === cap.source_component_id &&
				e.pin_number === 1,
		);
		const pinPort = records.find(
			(e) =>
				e.type === "source_port" &&
				e.source_component_id === processor.source_component_id &&
				e.pin_number === Number(cap.name.slice(3)),
		);
		const rail = records.find(
			(e) =>
				e.type === "source_trace" &&
				e.connected_source_port_ids.includes(capPort.source_port_id),
		);
		if (
			!rail ||
			!rail.connected_source_port_ids.includes(pinPort.source_port_id)
		)
			throw Error(`Missing decoupling target for ${cap.name}`);
		rail.connected_source_port_ids = rail.connected_source_port_ids.filter(
			(id: string) => id !== capPort.source_port_id,
		);
		records.push({
			type: "source_trace",
			source_trace_id: `source_trace_${cap.name}_direct`,
			name: `D_${cap.name}_U1_pin${pinPort.pin_number}`,
			connected_source_port_ids: [
				capPort.source_port_id,
				pinPort.source_port_id,
			],
			connected_source_net_ids: [],
			subcircuit_id: rail.subcircuit_id,
			min_trace_thickness: 0.12,
			max_length: 1000,
		});
	}
	// Explicit local nets give the schematic conventional named connections.
	// The existing source traces and stored copper retain the same endpoints.
	for (const e of [...json] as any[]) {
		if (e.type !== "source_trace" || !e.name?.startsWith("N_")) continue;
		const netName = e.name.slice(2);
		const id = `source_net_${netName}`;
		(json as any[]).push({
			type: "source_net",
			source_net_id: id,
			name: netName,
			member_source_group_ids: [],
		});
		e.connected_source_net_ids = [id];
	}

	return json
		.filter((e) => e.type !== "pcb_trace" && e.type !== "pcb_via")
		.map((e) =>
			// The pinned inflator lacks crystal and testpoint cases. Temporary
			// chips retain selectors until native components replace them.
			e.type === "source_component" &&
			["simple_crystal", "simple_test_point"].includes(e.ftype)
				? { ...e, ftype: "simple_chip" as const }
				: e,
		) as CircuitJson;
}

/** Four-layer, top-mounted, pre-routed F1C100S + decoupling module.
 * The package uses stored copper. No fanout solver runs during instantiation.
 */
export function F1C100SModule(props: F1C100SModuleProps) {
	if ("busProfile" in props || "variant" in props || "busExits" in props)
		throw new Error("Use layoutProfile to select a stored F1C100S layout");
	const {
		name,
		layoutProfile = "native",
		connections = {},
		schematicLayout = "default",
		...placement
	} = props;
	assertLayoutProfile(layoutProfile);
	if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
		throw new Error("Module name must be a selector-safe identifier");
	for (const port of Object.keys(connections))
		if (!EXTERNAL_NETS.includes(port))
			throw new Error(`Unknown F1C100S terminal '${port}'`);
	const selected = new Set(
		Object.entries(connections)
			.filter(([, target]) => Boolean(target))
			.map(([port]) => port),
	);
	const { circuitJson, paths } = selectBreakouts(
		getF1C100SCircuitJson(layoutProfile),
		selected,
	);
	return (
		<>
			<subcircuit
				name={name}
				{...{
					ref: (instance: Parameters<typeof attachSchematicLayout>[0]) =>
						attachSchematicLayout(
							instance,
							schematicLayout,
							layoutProfile,
							selected,
							paths,
						),
				}}
				minTraceWidth={0.12}
				minViaPadDiameter={0.45}
				minViaHoleDiameter={0.2}
				autorouter={{ local: true, algorithmFn: joinSavedPathExits }}
				schTraceAutoLabelEnabled
				schMaxTraceDistance={3}
				circuitJson={prepareForInflation(circuitJson)}
				{...placement}
			/>
			{Object.entries(connections).map(([port, target]) =>
				target ? (
					<trace
						key={port}
						name={`${name}_${port}_external`}
						from={`.${name} .${port} > .pin1`}
						to={target}
					/>
				) : null,
			)}
		</>
	);
}
export default F1C100SModule;