anil08607/Smartwatch

A 46 mm smartwatch PCB built around an nRF52810 BLE MCU, with USB‑C PD/Li‑ion charging, buck‑boost power, LCD, accelerometer, RTC, buttons, haptic motor, crystals, antenna, and SWD programming connector.

Version
1.0.14
License
unset
Stars
0

routing/native-viewer.ts

PCBPCB preview for routing/native-viewer.ts
SchematicSchematic preview for routing/native-viewer.ts
import type { Board } from "@tscircuit/core";
import type { AnyCircuitElement } from "circuit-json";
import reviewed from "./reviewed.circuit.json";

type RecordData = Record<string, any>;
const kinds = new Set(["pcb_trace", "pcb_via", "pcb_copper_pour"]);

// Fail after an electrical/placement edit instead of displaying stale copper.
export function nativeRoutingSignature(records: RecordData[]) {
	const labels = new Map<string, string>();
	for (const x of records.filter(
		(x) => x.type === "source_component" && x.ftype !== "subassembly",
	))
		labels.set(x.source_component_id, x.name);
	for (const x of records.filter((x) => x.type === "source_net"))
		labels.set(x.source_net_id, x.name);
	for (const x of records.filter((x) => x.type === "source_port"))
		labels.set(
			x.source_port_id,
			`${labels.get(x.source_component_id)}.${x.pin_number ?? x.name}`,
		);
	for (const x of records.filter((x) => x.type === "pcb_component"))
		labels.set(x.pcb_component_id, labels.get(x.source_component_id)!);
	for (const x of records.filter((x) => x.type === "pcb_port"))
		labels.set(x.pcb_port_id, labels.get(x.source_port_id)!);
	const keep = new Set([
		"source_component",
		"source_port",
		"source_net",
		"source_trace",
		"pcb_board",
		"pcb_component",
		"pcb_port",
		"pcb_smtpad",
		"pcb_plated_hole",
		"pcb_hole",
	]);
	const normalize = (x: any): any => {
		if (typeof x === "number") return Number(x.toPrecision(12));
		if (typeof x === "string") return labels.get(x) ?? x;
		if (Array.isArray(x)) return x.map(normalize);
		if (x && typeof x === "object")
			return Object.fromEntries(
				Object.entries(x)
					.filter(
						([k]) =>
							!k.endsWith("_id") &&
							!k.includes("connectivity_map") &&
							k !== "pcb_port_ids" &&
							k !== "source_group_ids" &&
							k !== "source_component_ids" &&
							k !== "source_trace_ids" &&
							// The optional parts engine changes this annotation, not pad geometry.
							k !== "pin1_location",
					)
					.sort(([a], [b]) => a.localeCompare(b))
					.map(([k, v]) => [k, normalize(v)]),
			);
		return x;
	};
	return records
		.filter((x) => keep.has(x.type) && x.ftype !== "subassembly")
		.map((x) =>
			JSON.stringify({
				...normalize(x),
				owner: labels.get(x.source_component_id ?? x.pcb_component_id),
				port: labels.get(x.source_port_id ?? x.pcb_port_id),
			}),
		)
		.sort()
		.join("\n");
}

/** Replace only routing; the normal source, placement, schematic and DRC phases still run. */
export function installNativeRouting(board: Board | null) {
	if (!board) return;
	board.doInitialPcbTraceRender = () => {
		const db = board.root!.db;
		const current = db.toArray() as RecordData[];
		const actual = nativeRoutingSignature(current),
			expected = nativeRoutingSignature(reviewed);
		if (actual !== expected) {
			const actualRows = actual.split("\n"),
				expectedRows = expected.split("\n");
			const actualSet = new Set(actualRows),
				expectedSet = new Set(expectedRows);
			throw new Error(
				`Reviewed native routing is stale. Current-only: ${actualRows
					.filter((x) => !expectedSet.has(x))
					.slice(0, 2)
					.join("; ")}. Reviewed-only: ${expectedRows
					.filter((x) => !actualSet.has(x))
					.slice(0, 2)
					.join("; ")}`,
			);
		}
		const ids = new Map<string, string>();
		for (const type of ["source_net", "source_trace"]) {
			const key = (x: RecordData) =>
				type === "source_net"
					? x.name
					: JSON.stringify([
							x.connected_source_port_ids,
							x.connected_source_net_ids,
						]);
			for (const x of reviewed.filter((x) => x.type === type) as RecordData[]) {
				const match = current.find((y) => y.type === type && key(y) === key(x));
				if (!match) throw new Error(`Native route identity mismatch: ${type}`);
				ids.set(x[`${type}_id`], match[`${type}_id`]);
			}
		}
		for (const type of ["pcb_trace", "pcb_via", "pcb_copper_pour"] as const)
			for (const x of db[type].list())
				db[type].delete((x as RecordData)[`${type}_id`]);
		const copper = reviewed
			.filter((x) => kinds.has(x.type))
			.map((x) => {
				const copy = structuredClone(x) as RecordData;
				copy.subcircuit_id = board.subcircuit_id;
				for (const field of ["source_net_id", "source_trace_id"])
					if (copy[field]) copy[field] = ids.get(copy[field]);
				return copy as AnyCircuitElement;
			});
		db.insertAll(copper);
	};
}