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