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
src/selective-breakouts.ts
import type { CircuitJson } from "circuit-json";
import type { FanoutTracePath } from "@tscircuit/props";
import { EXTERNAL_NETS } from "./pin-map";
const reverse = (route: any[]) => route.toReversed().map(p =>
p.route_type === "via" ? {...p, from_layer: p.to_layer, to_layer: p.from_layer} : {...p});
/** Re-root stored copper at an internal port when an exit is not requested.
* Internal support branches are retained, including junctions at former exits.
* No new routing solver runs and no unrequested breakout components survive.
*/
export function selectBreakouts(original: CircuitJson, selected: ReadonlySet<string>) {
const json: any[] = structuredClone(original);
const components = new Map(json.filter(e => e.type === "source_component").map(e => [e.source_component_id, e]));
const ports = new Map(json.filter(e => e.type === "source_port").map(e => [e.source_port_id, e]));
const name = (p: any) => components.get(ports.get(p.source_port_id).source_component_id).name;
const isRemoved = (c: any) => EXTERNAL_NETS.includes(c.name) && !selected.has(c.name);
const removedPorts = new Set([...ports.values()].filter(p => isRemoved(components.get(p.source_component_id))).map(p => p.source_port_id));
const paths: FanoutTracePath[] = [];
const droppedTraces = new Set<string>();
for (const net of json.filter(e => e.type === "source_trace")) {
const members = json.filter(e => e.type === "pcb_port" && net.connected_source_port_ids.includes(e.source_port_id));
const retained = members.filter(p => !removedPorts.has(p.source_port_id));
net.connected_source_port_ids = net.connected_source_port_ids.filter((id: string) => !removedPorts.has(id));
if (retained.length < 2) { droppedTraces.add(net.source_trace_id); continue; }
const root = retained.find(p => EXTERNAL_NETS.includes(name(p))) ?? retained.find(p => name(p) === "Y1") ?? retained[0];
const graph = new Map<string, {to: string; route: any[]}[]>();
for (const trace of json.filter(e => e.type === "pcb_trace" && e.source_trace_id === net.source_trace_id)) {
const route = trace.route.map((p: any) => p.route_type === "wire"
? {route_type: "wire", x:p.x, y:p.y, width:p.width, layer:p.layer}
: {route_type:"via", x:p.x, y:p.y, from_layer:p.from_layer, to_layer:p.to_layer, via_diameter:0.45, via_hole_diameter:0.2});
const find = (q: any) => members.find(p => Math.hypot(p.x-q.x,p.y-q.y)<1e-4 && p.layers.includes(q.layer));
const a = find(route[0]), b = find(route.at(-1));
if (!a || !b) throw Error(`Missing stored endpoint for ${net.name}`);
for (const [from,to,r] of [[a,b,route],[b,a,reverse(route)]] as any[]) {
if (!graph.has(from.pcb_port_id)) graph.set(from.pcb_port_id, []);
graph.get(from.pcb_port_id)!.push({to:to.pcb_port_id,route:r});
}
}
const routes = new Map<string, any[]>([[root.pcb_port_id, []]]);
const queue = [root.pcb_port_id];
while(queue.length) {
const id = queue.shift()!;
for (const edge of graph.get(id) ?? []) {
if(routes.has(edge.to)) continue;
const prefix = reverse(edge.route), suffix = routes.get(id)!;
const a = prefix.at(-1), b = suffix[0];
// A removed plated terminal can be a junction between internal branches.
// Preserve its layer transition as a via, without retaining a testpoint.
const bridge = b && a.layer !== b.layer ? [{route_type:"via",x:a.x,y:a.y,from_layer:a.layer,to_layer:b.layer,via_diameter:0.45,via_hole_diameter:0.2}] : [];
routes.set(edge.to,[...prefix,...bridge,...(bridge.length ? suffix : suffix.slice(1))]);
queue.push(edge.to);
}
}
for (const port of retained) {
if(port === root) continue;
const route = routes.get(port.pcb_port_id);
if(!route) throw Error(`Disconnected stored net ${net.name}`);
paths.push({connection:`${name(port)}.pin${ports.get(port.source_port_id).pin_number}`,route} as FanoutTracePath);
}
}
// Remove the exit and all records owned by its source/PCB/schematic IDs.
const removedIds = new Set<string>([...components.values()].filter(isRemoved).map(c => c.source_component_id));
for(const id of droppedTraces) removedIds.add(id);
let changed = true;
while(changed) {
changed = false;
for(const e of json) {
if(Object.entries(e).some(([key,value]) => key.endsWith("_id") && removedIds.has(value as string))) {
const id = e[`${e.type}_id`];
if(id && !removedIds.has(id)) {removedIds.add(id);changed=true;}
}
}
}
const circuitJson = json.filter(e => e.type !== "pcb_trace" && e.type !== "pcb_via" && !Object.entries(e).some(([key,value]) => key.endsWith("_id") && removedIds.has(value as string)));
return {circuitJson: circuitJson as CircuitJson, paths};
}