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};
}