diff --git a/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts b/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts index 780f2c5b2..bf0685af9 100644 --- a/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts +++ b/lib/components/primitive-components/Group/Group_getRoutingPhasePlans.ts @@ -128,13 +128,14 @@ function traceHasEndpointMatchingConnectionSelector( trace: Trace, connectionSelectorEndpointKey: string, ): boolean { - return trace - .getTracePortPathSelectors() - .some( - (selector) => - convertPortSelectorToEndpointKey(selector) === - connectionSelectorEndpointKey, - ) + return [ + ...trace.getTracePortPathSelectors(), + ...trace.getTracePathNetSelectors(), + ].some( + (selector) => + convertPortSelectorToEndpointKey(selector) === + connectionSelectorEndpointKey, + ) } function getAutoroutersByPhaseIndex( @@ -307,6 +308,19 @@ export function Group_getRoutingPhasePlans( convertPortSelectorToEndpointKey, ) plan.connectionSelectors = connectionSelectors + for (const selector of connectionSelectors) { + const net = group.selectOne(selector, { type: "net" }) + if (!net || breakoutByNet.has(net)) continue + if (!phaseProps.reroute) { + for (const existingPlan of plansByPhaseIndex.values()) { + if (existingPlan === plan) continue + existingPlan.nets = existingPlan.nets.filter( + (existingNet) => existingNet !== net, + ) + } + } + if (!plan.nets.includes(net)) plan.nets.push(net) + } for (const trace of traces) { if (breakoutByTrace.has(trace)) continue diff --git a/lib/components/primitive-components/Group/Group_phasedAutoroutingUtils.ts b/lib/components/primitive-components/Group/Group_phasedAutoroutingUtils.ts index f41543fdf..a8141a41a 100644 --- a/lib/components/primitive-components/Group/Group_phasedAutoroutingUtils.ts +++ b/lib/components/primitive-components/Group/Group_phasedAutoroutingUtils.ts @@ -21,6 +21,12 @@ export function connectionIsInRoutingPhase( for (const trace of phasePlan.traces) { if (!trace.source_trace_id) continue if (connection.source_trace_id === trace.source_trace_id) return true + // Breakouts use their split, group-owned connections, not the whole net. + if ( + !phasePlan.routingPcbGroupId && + connection.source_trace_ids?.includes(trace.source_trace_id) + ) + return true if (connection.name === trace.source_trace_id) return true if (connection.rootConnectionName === trace.source_trace_id) return true if (connection.mergedConnectionNames?.includes(trace.source_trace_id)) { diff --git a/lib/utils/autorouting/SimpleRouteJson.ts b/lib/utils/autorouting/SimpleRouteJson.ts index cd872a580..7d574011b 100644 --- a/lib/utils/autorouting/SimpleRouteJson.ts +++ b/lib/utils/autorouting/SimpleRouteJson.ts @@ -2,7 +2,7 @@ import type { SimpleRouteJson as AutorouterSimpleRouteJson, SimplifiedPcbTrace as AutorouterSimplifiedPcbTrace, } from "@tscircuit/capacity-autorouter" -import type { PcbGroup } from "circuit-json" +import type { PcbGroup, SourceTrace } from "circuit-json" import type { CircuitJsonMetadata, Obstacle } from "../obstacles/types" export type { CircuitJsonMetadata, Obstacle } from "../obstacles/types" @@ -83,6 +83,8 @@ export type SimpleRouteConnection = { name: SrjConnectionName routingPcbGroupId?: PcbGroupId source_trace_id?: string + /** Source traces combined into a net-based routing connection. */ + source_trace_ids?: SourceTrace["source_trace_id"][] rootConnectionName?: string mergedConnectionNames?: string[] isOffBoard?: boolean diff --git a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts index b24dcd341..ceadbcbca 100644 --- a/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts +++ b/lib/utils/autorouting/getDifferentialPairsForSimpleRouteJson.ts @@ -137,8 +137,13 @@ const getDifferentialPairSrjConnectionNamesByCohortOrThrow = ({ const matchingSrjConnections: SimpleRouteConnection[] = [] for (const srjConnection of srjConnections) { if ( - srjConnection.source_trace_id && - differentialPairSourceTraceIds.includes(srjConnection.source_trace_id) + (srjConnection.source_trace_id && + differentialPairSourceTraceIds.includes( + srjConnection.source_trace_id, + )) || + srjConnection.source_trace_ids?.some((sourceTraceId) => + differentialPairSourceTraceIds.includes(sourceTraceId), + ) ) { matchingSrjConnections.push(srjConnection) } diff --git a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts index 971b5e5bc..1018ab7f3 100644 --- a/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts +++ b/lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson.ts @@ -709,6 +709,9 @@ export const getSimpleRouteJsonFromCircuitJson = ({ name: net.source_net_id ?? sharedConnMap.getNetConnectedToId(net.source_net_id), + source_trace_ids: connectedSourceTraces.map( + (sourceTrace) => sourceTrace.source_trace_id, + ), nominalTraceWidth: nominalTraceWidthFromConnectedTraces, width: nominalTraceWidthFromConnectedTraces, pointsToConnect, diff --git a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx index c35b9713b..0880abbca 100644 --- a/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx +++ b/tests/components/primitive-components/differential-pair/net-fragments-point-to-point.test.tsx @@ -1,4 +1,5 @@ import { expect, test } from "bun:test" +import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" import { getTestFixture } from "tests/fixtures/get-test-fixture" test("does not warn for a two-terminal conductor split through a source net", (): void => { @@ -30,4 +31,20 @@ test("does not warn for a two-terminal conductor split through a source net", () warning.property_name === "negativeConnection", ), ).toEqual([]) + + const board = circuit.firstChild + if (!board) throw new Error("Expected a board") + const { simpleRouteJson } = getSimpleRouteJsonFromCircuitJson({ + db: circuit.db, + subcircuitComponent: board, + }) + expect(simpleRouteJson.differentialPairs).toEqual([ + { + connectionNames: [ + circuit.db.source_net.getWhere({ name: "DP" })!.source_net_id, + circuit.db.source_trace.getWhere({ name: "DM" })!.source_trace_id, + ], + lengthTolerance: 0.1, + }, + ]) }) diff --git a/tests/repros/__snapshots__/usb-c-breakout-net-pair-pcb.snap.svg b/tests/repros/__snapshots__/usb-c-breakout-net-pair-pcb.snap.svg index c3f43b019..a940c24bf 100644 --- a/tests/repros/__snapshots__/usb-c-breakout-net-pair-pcb.snap.svg +++ b/tests/repros/__snapshots__/usb-c-breakout-net-pair-pcb.snap.svg @@ -1 +1 @@ -J1VBUSDMDPGNDJ2R1R2R3R4C1USB-C DEVICE BREAKOUTCC pull-downs: 5.1k \ No newline at end of file +J1VBUSDMDPGNDJ2R1R2R3R4C1USB-C DEVICE BREAKOUTCC pull-downs: 5.1kautorouting phase 0autorouting phase 1 \ No newline at end of file diff --git a/tests/repros/usb-c-breakout-net-pair.test.tsx b/tests/repros/usb-c-breakout-net-pair.test.tsx index d6b225c22..ad867e694 100644 --- a/tests/repros/usb-c-breakout-net-pair.test.tsx +++ b/tests/repros/usb-c-breakout-net-pair.test.tsx @@ -1,25 +1,21 @@ import { expect, test } from "bun:test" -import { getSimpleRouteJsonFromCircuitJson } from "lib/utils/autorouting/getSimpleRouteJsonFromCircuitJson" +import { createAutoroutingPhaseIoStack } from "tests/fixtures/create-autorouting-phase-io-stack" import { getTestFixture } from "tests/fixtures/get-test-fixture" import UsbBreakoutBoard from "./usb-c-breakout/board" test("USB-C breakout pair wired through named nets", async () => { const { circuit } = getTestFixture() + const phases = createAutoroutingPhaseIoStack(circuit) circuit.add() await circuit.renderUntilSettled() expect(circuit.db.source_failed_to_create_component_error.list()).toEqual([]) expect(circuit.db.source_trace_not_connected_error.list()).toEqual([]) - const board = circuit.firstChild - if (!board) throw new Error("Expected USB-C breakout board") - expect(() => - getSimpleRouteJsonFromCircuitJson({ - db: circuit.db, - subcircuitComponent: board, - }), - ).toThrow( - 'Could not find an SRJ connection for trace name or port selector ".R3 > .pin1"', - ) - expect(circuit.db.pcb_trace.list()).toHaveLength(0) + expect(phases).toHaveLength(2) + expect(phases[0]?.startSimpleRouteJson?.differentialPairs).toHaveLength(1) + expect(phases[0]?.endSimpleRouteJson?.traces).toHaveLength(2) + expect(circuit.db.pcb_autorouting_error.list()).toEqual([]) + expect(circuit.db.pcb_trace_error.list()).toEqual([]) + expect(circuit.db.pcb_port_not_connected_error.list()).toEqual([]) await expect(circuit).toMatchPcbSnapshot(import.meta.path) }, 30_000)