@tscircuit/core 0.0.1832 → 0.0.1834

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1406 -658
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -19990,6 +19990,345 @@ function getFanoutExitPositionConfig(exitPosition) {
19990
19990
  // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
19991
19991
  import { BaseSolver } from "@tscircuit/solver-utils";
19992
19992
 
19993
+ // node_modules/@tscircuit/fanout-solver/lib/select-compatible-candidates.ts
19994
+ function selectCompatibleCandidates(params) {
19995
+ const candidates = params.candidateSets.flat();
19996
+ const count = candidates.length;
19997
+ const compatibility = new Uint8Array(count * count);
19998
+ const emptyDomainIndices = /* @__PURE__ */ new Set();
19999
+ const revisionParent = /* @__PURE__ */ new Map();
20000
+ let nextIndex = 0;
20001
+ const initialDomains = params.candidateSets.map(
20002
+ (set) => set.map(() => nextIndex++)
20003
+ );
20004
+ let searchStates = 0;
20005
+ const compatible = (first, second) => {
20006
+ const index = Math.min(first, second) * count + Math.max(first, second);
20007
+ const cached = compatibility[index];
20008
+ if (cached) return cached === 1;
20009
+ const result2 = params.areCompatible(candidates[first], candidates[second]);
20010
+ compatibility[index] = result2 ? 1 : 2;
20011
+ return result2;
20012
+ };
20013
+ const neighbors = initialDomains.map(() => []);
20014
+ for (let first = 0; first < initialDomains.length; first++) {
20015
+ for (let second = first + 1; second < initialDomains.length; second++) {
20016
+ if (initialDomains[first].some(
20017
+ (a) => initialDomains[second].some((b) => !compatible(a, b))
20018
+ )) {
20019
+ neighbors[first].push(second);
20020
+ neighbors[second].push(first);
20021
+ }
20022
+ }
20023
+ }
20024
+ const propagate = (domains, indices, changed) => {
20025
+ const queue = [];
20026
+ let allDomainsRemainNonempty = true;
20027
+ for (const first of indices) {
20028
+ if (domains[first].length === 0) {
20029
+ emptyDomainIndices.add(first);
20030
+ allDomainsRemainNonempty = false;
20031
+ continue;
20032
+ }
20033
+ for (const second of neighbors[first]) {
20034
+ if (indices.includes(second) && (changed === void 0 || second === changed))
20035
+ queue.push([first, second]);
20036
+ }
20037
+ }
20038
+ for (let cursor = 0; cursor < queue.length; cursor++) {
20039
+ const [first, second] = queue[cursor];
20040
+ if (domains[first].length === 0 || domains[second].length === 0)
20041
+ continue;
20042
+ const retained = domains[first].filter(
20043
+ (candidate) => domains[second].some((other) => compatible(candidate, other))
20044
+ );
20045
+ if (retained.length === domains[first].length) continue;
20046
+ revisionParent.set(first, second);
20047
+ domains[first] = retained;
20048
+ if (retained.length === 0) {
20049
+ emptyDomainIndices.add(first);
20050
+ allDomainsRemainNonempty = false;
20051
+ continue;
20052
+ }
20053
+ for (const neighbor of neighbors[first]) {
20054
+ if (neighbor !== second && indices.includes(neighbor))
20055
+ queue.push([neighbor, first]);
20056
+ }
20057
+ }
20058
+ return allDomainsRemainNonempty;
20059
+ };
20060
+ const allIndices = initialDomains.map((_, index) => index);
20061
+ const getConflictDomainIndices = (seeds) => {
20062
+ const pending = [...seeds];
20063
+ const seen = new Set(pending);
20064
+ for (let cursor = 0; cursor < pending.length; cursor++) {
20065
+ const parent = revisionParent.get(pending[cursor]);
20066
+ if (parent !== void 0 && !seen.has(parent)) {
20067
+ seen.add(parent);
20068
+ pending.push(parent);
20069
+ }
20070
+ }
20071
+ return [...seen];
20072
+ };
20073
+ const rootDomains = initialDomains.map((domain) => [...domain]);
20074
+ if (!propagate(rootDomains, allIndices)) {
20075
+ return {
20076
+ selection: null,
20077
+ searchStates,
20078
+ emptyDomainIndices: [...emptyDomainIndices],
20079
+ conflictDomainIndices: getConflictDomainIndices([...emptyDomainIndices])
20080
+ };
20081
+ }
20082
+ const findLocallyCompatibleSelection = () => {
20083
+ const restartCount = 12;
20084
+ const stepsPerRestart = Math.max(
20085
+ 1,
20086
+ Math.min(5e3, Math.floor(params.maximumSearchStates / restartCount))
20087
+ );
20088
+ for (let restart = 0; restart < restartCount; restart++) {
20089
+ const selection = rootDomains.map(
20090
+ (domain, index) => domain[(restart * 17 + index * 7) % domain.length]
20091
+ );
20092
+ for (let step = 0; step < stepsPerRestart; step++) {
20093
+ const conflictCounts = selection.map(() => 0);
20094
+ for (let first = 0; first < selection.length; first++) {
20095
+ for (const second of neighbors[first]) {
20096
+ if (second <= first || compatible(selection[first], selection[second]))
20097
+ continue;
20098
+ conflictCounts[first]++;
20099
+ conflictCounts[second]++;
20100
+ }
20101
+ }
20102
+ const maximumConflictCount = Math.max(...conflictCounts);
20103
+ if (maximumConflictCount === 0) return selection;
20104
+ const conflicted = conflictCounts.map((conflicts, index) => ({ conflicts, index })).filter(({ conflicts }) => conflicts === maximumConflictCount);
20105
+ const selected = conflicted[(step + restart) % conflicted.length].index;
20106
+ const scored = rootDomains[selected].map((candidate, index) => ({
20107
+ candidate,
20108
+ index,
20109
+ conflicts: neighbors[selected].filter(
20110
+ (neighbor) => !compatible(candidate, selection[neighbor])
20111
+ ).length
20112
+ })).toSorted(
20113
+ (first, second) => first.conflicts - second.conflicts || (first.index - step - restart) % rootDomains[selected].length - (second.index - step - restart) % rootDomains[selected].length
20114
+ );
20115
+ selection[selected] = scored[0].candidate;
20116
+ }
20117
+ }
20118
+ return null;
20119
+ };
20120
+ const locallyCompatibleSelection = findLocallyCompatibleSelection();
20121
+ if (locallyCompatibleSelection) {
20122
+ return {
20123
+ selection: locallyCompatibleSelection.map((index) => candidates[index]),
20124
+ searchStates,
20125
+ emptyDomainIndices: [],
20126
+ conflictDomainIndices: []
20127
+ };
20128
+ }
20129
+ const search = (domains, indices, changed) => {
20130
+ if (!propagate(domains, indices, changed)) return null;
20131
+ const pending = new Set(
20132
+ indices.filter((index) => domains[index].length > 1)
20133
+ );
20134
+ const components = [];
20135
+ while (pending.size > 0) {
20136
+ const component = [pending.values().next().value];
20137
+ pending.delete(component[0]);
20138
+ for (let cursor = 0; cursor < component.length; cursor++) {
20139
+ for (const neighbor of neighbors[component[cursor]]) {
20140
+ if (pending.delete(neighbor)) component.push(neighbor);
20141
+ }
20142
+ }
20143
+ components.push(component);
20144
+ }
20145
+ if (components.length > 1) {
20146
+ let combined = domains;
20147
+ for (const component of components) {
20148
+ const solved = search(combined.slice(), component);
20149
+ if (!solved) return null;
20150
+ combined = solved;
20151
+ }
20152
+ return combined;
20153
+ }
20154
+ let selected = -1;
20155
+ for (const index of indices) {
20156
+ if (domains[index].length > 1 && (selected < 0 || domains[index].length < domains[selected].length))
20157
+ selected = index;
20158
+ }
20159
+ if (selected < 0) return domains;
20160
+ const orderedCandidates = domains[selected].map((candidate) => {
20161
+ const supportCounts = neighbors[selected].filter(
20162
+ (neighbor) => indices.includes(neighbor) && domains[neighbor].length > 1
20163
+ ).map(
20164
+ (neighbor) => domains[neighbor].filter((other) => compatible(candidate, other)).length
20165
+ );
20166
+ return {
20167
+ candidate,
20168
+ minimumSupport: Math.min(...supportCounts),
20169
+ totalSupport: supportCounts.reduce((sum, count2) => sum + count2, 0)
20170
+ };
20171
+ }).toSorted(
20172
+ (first, second) => second.minimumSupport - first.minimumSupport || second.totalSupport - first.totalSupport
20173
+ );
20174
+ for (const { candidate } of orderedCandidates) {
20175
+ if (searchStates >= params.maximumSearchStates) return null;
20176
+ searchStates++;
20177
+ const nextDomains = domains.slice();
20178
+ nextDomains[selected] = [candidate];
20179
+ const result2 = search(nextDomains, indices, selected);
20180
+ if (result2) return result2;
20181
+ }
20182
+ return null;
20183
+ };
20184
+ const result = search(rootDomains, allIndices);
20185
+ const diagnosticIndices = emptyDomainIndices.size > 0 || result ? [...emptyDomainIndices] : [
20186
+ allIndices.toSorted(
20187
+ (first, second) => neighbors[second].length - neighbors[first].length || rootDomains[first].length - rootDomains[second].length
20188
+ )[0]
20189
+ ];
20190
+ return {
20191
+ selection: result?.map((domain) => candidates[domain[0]]) ?? null,
20192
+ searchStates,
20193
+ emptyDomainIndices: diagnosticIndices,
20194
+ conflictDomainIndices: result ? [] : getConflictDomainIndices(diagnosticIndices)
20195
+ };
20196
+ }
20197
+
20198
+ // node_modules/@tscircuit/fanout-solver/lib/refine-adaptive-plane-reservations.ts
20199
+ var EPSILON5 = 1e-9;
20200
+ function refineAdaptivePlaneReservationCore(params) {
20201
+ const core = params.candidateBusIds.filter(
20202
+ (busId) => !params.activeBusIds.has(busId)
20203
+ );
20204
+ if (core.length < 2) return core;
20205
+ const terminalIndex = core.length - 1;
20206
+ const terminal = params.planeBuses.find(
20207
+ (bus) => bus.busId === core[terminalIndex]
20208
+ );
20209
+ const source = terminal?.connections[0]?.sourcePoint;
20210
+ if (!terminal || !source || terminal.connections.length !== 1 || terminal.termination.type !== "plane")
20211
+ return core;
20212
+ const terminalLayer = terminal.termination.layer;
20213
+ const perpendicularAxis = terminal.direction === "up" || terminal.direction === "down" ? "x" : "y";
20214
+ const parallelAxis = perpendicularAxis === "x" ? "y" : "x";
20215
+ const componentCoordinates = perpendicularAxis === "x" ? terminal.xCoordinates : terminal.yCoordinates;
20216
+ const pitch = perpendicularAxis === "x" ? terminal.pitchX : terminal.pitchY;
20217
+ if (componentCoordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON5) {
20218
+ return core;
20219
+ }
20220
+ const componentCenter = (Math.min(...componentCoordinates) + Math.max(...componentCoordinates)) / 2;
20221
+ const inwardSign = Math.sign(componentCenter - source[perpendicularAxis]);
20222
+ if (inwardSign === 0) return core;
20223
+ const coreIds = new Set(core);
20224
+ const replacement = params.planeBuses.filter((bus) => {
20225
+ const candidateSource = bus.connections[0]?.sourcePoint;
20226
+ if (bus === terminal || bus.componentId !== terminal.componentId || bus.termination.type !== "plane" || bus.termination.layer !== terminalLayer || bus.direction !== terminal.direction || bus.connections.length !== 1 || !candidateSource || params.activeBusIds.has(bus.busId) || coreIds.has(bus.busId) || Math.abs(candidateSource[parallelAxis] - source[parallelAxis]) > EPSILON5) {
20227
+ return false;
20228
+ }
20229
+ const inwardDistance = (candidateSource[perpendicularAxis] - source[perpendicularAxis]) * inwardSign;
20230
+ return inwardDistance > EPSILON5 && inwardDistance <= pitch + EPSILON5;
20231
+ }).toSorted((first, second) => {
20232
+ const firstSource = first.connections[0].sourcePoint;
20233
+ const secondSource = second.connections[0].sourcePoint;
20234
+ return Math.abs(firstSource[perpendicularAxis] - source[perpendicularAxis]) - Math.abs(
20235
+ secondSource[perpendicularAxis] - source[perpendicularAxis]
20236
+ ) || first.connections[0].connectionIndex - second.connections[0].connectionIndex;
20237
+ })[0];
20238
+ if (!replacement) return core;
20239
+ return core.map(
20240
+ (busId, index) => index === terminalIndex ? replacement.busId : busId
20241
+ );
20242
+ }
20243
+
20244
+ // node_modules/@tscircuit/fanout-solver/lib/should-use-adaptive-dense-plane-routing.ts
20245
+ function getSourceFieldFacingEdge(buses) {
20246
+ const firstBus = buses[0];
20247
+ if (!firstBus || buses.some((bus) => bus.componentId !== firstBus.componentId))
20248
+ return void 0;
20249
+ const sourcePoints = buses.flatMap(
20250
+ (bus) => bus.connections.map((connection) => connection.sourcePoint)
20251
+ );
20252
+ if (sourcePoints.length === 0) return void 0;
20253
+ const center = sourcePoints.reduce(
20254
+ (sum, point6) => ({ x: sum.x + point6.x, y: sum.y + point6.y }),
20255
+ { x: 0, y: 0 }
20256
+ );
20257
+ center.x /= sourcePoints.length;
20258
+ center.y /= sourcePoints.length;
20259
+ const { minX, maxX, minY, maxY } = firstBus.componentBounds;
20260
+ const edgesByDistance = [
20261
+ { edge: "left", distance: Math.abs(center.x - minX) },
20262
+ { edge: "right", distance: Math.abs(maxX - center.x) },
20263
+ { edge: "bottom", distance: Math.abs(center.y - minY) },
20264
+ { edge: "top", distance: Math.abs(maxY - center.y) }
20265
+ ].toSorted((first, second) => first.distance - second.distance);
20266
+ if (Math.abs(edgesByDistance[0].distance - edgesByDistance[1].distance) <= 1e-9)
20267
+ return void 0;
20268
+ return edgesByDistance[0].edge;
20269
+ }
20270
+ function shouldUseAdaptiveDensePlaneRouting(buses, allowBlindAndBuriedVias) {
20271
+ if (allowBlindAndBuriedVias) return false;
20272
+ const boundaryBuses = buses.filter(
20273
+ (bus) => bus.termination.type === "boundary"
20274
+ );
20275
+ const singletonBoundaryBuses = boundaryBuses.filter(
20276
+ (bus) => bus.connections.length === 1
20277
+ );
20278
+ const pairBoundaryBuses = boundaryBuses.filter(
20279
+ (bus) => bus.connections.length === 2
20280
+ );
20281
+ const wideBoundaryBuses = boundaryBuses.filter(
20282
+ (bus) => bus.connections.length >= 8
20283
+ );
20284
+ const planeCount = buses.filter(
20285
+ (bus) => bus.termination.type === "plane" && bus.connections.length === 1
20286
+ ).length;
20287
+ const sharedExitEdge = wideBoundaryBuses[0]?.exitEdge;
20288
+ if (boundaryBuses.length !== 9 || singletonBoundaryBuses.length !== 3 || pairBoundaryBuses.length !== 3 || wideBoundaryBuses.length !== 3 || planeCount < 64 || sharedExitEdge === void 0 || wideBoundaryBuses.some((bus) => bus.exitEdge !== sharedExitEdge))
20289
+ return false;
20290
+ const sourceFacingEdge = getSourceFieldFacingEdge(wideBoundaryBuses);
20291
+ if (sourceFacingEdge === void 0) return false;
20292
+ const sourceFacesHorizontalEdge = sourceFacingEdge === "left" || sourceFacingEdge === "right";
20293
+ const exitUsesHorizontalEdge = sharedExitEdge === "left" || sharedExitEdge === "right";
20294
+ return sourceFacesHorizontalEdge !== exitUsesHorizontalEdge;
20295
+ }
20296
+
20297
+ // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
20298
+ import { mergeGraphics } from "graphics-debug";
20299
+
20300
+ // node_modules/@tscircuit/fanout-solver/lib/add-via-layer-metadata.ts
20301
+ function addViaLayerMetadataToTrace(params) {
20302
+ const { trace, layerNames, allowBlindAndBuriedVias } = params;
20303
+ return {
20304
+ ...trace,
20305
+ route: trace.route.map(
20306
+ (routePoint) => routePoint.route_type === "via" && !allowBlindAndBuriedVias ? {
20307
+ ...routePoint,
20308
+ layers: getViaSpanLayers2({
20309
+ fromLayer: routePoint.from_layer,
20310
+ toLayer: routePoint.to_layer,
20311
+ layerNames,
20312
+ allowBlindAndBuriedVias
20313
+ })
20314
+ } : { ...routePoint }
20315
+ )
20316
+ };
20317
+ }
20318
+ function addViaLayerMetadataToSrj(params) {
20319
+ const { srj, layerNames, allowBlindAndBuriedVias } = params;
20320
+ return {
20321
+ ...srj,
20322
+ traces: (srj.traces ?? []).map(
20323
+ (trace) => addViaLayerMetadataToTrace({
20324
+ trace,
20325
+ layerNames,
20326
+ allowBlindAndBuriedVias
20327
+ })
20328
+ )
20329
+ };
20330
+ }
20331
+
19993
20332
  // node_modules/@tscircuit/fanout-solver/lib/boundary-exit.ts
19994
20333
  function getExitEdgeForDirection(direction) {
19995
20334
  switch (direction) {
@@ -20027,117 +20366,284 @@ function getCornerBandSide(exitEdge, preferredExit) {
20027
20366
  return preferredExit.endsWith("-right") ? "maximum" : "minimum";
20028
20367
  }
20029
20368
 
20030
- // node_modules/@tscircuit/fanout-solver/lib/get-routed-trace-copper.ts
20031
- function getRoutedTraceCopper(srj, trace, allowBlindAndBuriedVias = true) {
20032
- const layerNames = getCopperLayerNames(srj.layerCount);
20033
- const segments = [];
20034
- const vias = [];
20035
- let previousWire;
20036
- for (const routePoint of trace.route) {
20037
- if (routePoint.route_type === "via") {
20038
- vias.push({
20039
- center: { x: routePoint.x, y: routePoint.y },
20040
- diameter: routePoint.via_diameter ?? srj.minViaPadDiameter ?? srj.min_via_pad_diameter ?? srj.minViaDiameter ?? srj.minTraceWidth,
20041
- spanLayers: getRouteViaSpanLayers({
20042
- fromLayer: routePoint.from_layer,
20043
- toLayer: routePoint.to_layer,
20044
- layers: "layers" in routePoint && Array.isArray(routePoint.layers) ? routePoint.layers : void 0,
20045
- layerNames,
20046
- allowBlindAndBuriedVias
20047
- })
20048
- });
20049
- previousWire = void 0;
20050
- continue;
20051
- }
20052
- if (routePoint.route_type !== "wire") {
20053
- previousWire = void 0;
20054
- continue;
20055
- }
20056
- if (previousWire?.layer === routePoint.layer && (previousWire.x !== routePoint.x || previousWire.y !== routePoint.y)) {
20057
- segments.push({
20058
- start: { x: previousWire.x, y: previousWire.y },
20059
- end: { x: routePoint.x, y: routePoint.y },
20060
- width: Math.max(previousWire.width, routePoint.width),
20061
- layer: routePoint.layer
20062
- });
20369
+ // node_modules/@tscircuit/fanout-solver/lib/build-output.ts
20370
+ function createViaObstacle(plan, layerNames, via, viaIndex) {
20371
+ const zLayers = via.spanLayers.map((layer) => {
20372
+ const layerIndex = layerNames.indexOf(layer);
20373
+ if (layerIndex < 0) {
20374
+ throw new Error(
20375
+ `FanoutSolver: via for "${plan.connectionName}" uses unknown layer "${layer}"`
20376
+ );
20063
20377
  }
20064
- previousWire = routePoint;
20065
- }
20378
+ return layerIndex;
20379
+ });
20380
+ const outputIds = createFanoutOutputIds(plan);
20066
20381
  return {
20067
- trace,
20068
- connectionName: trace.connection_name,
20069
- segments,
20070
- vias
20382
+ obstacleId: viaIndex === "endpoint" ? outputIds.planeEndpointViaObstacleId : viaIndex === 0 ? outputIds.viaObstacleId : `${outputIds.viaObstacleId}:${viaIndex}`,
20383
+ type: "rect",
20384
+ center: via.center,
20385
+ width: via.diameter,
20386
+ height: via.diameter,
20387
+ layers: via.spanLayers,
20388
+ zLayers,
20389
+ __zLayers: zLayers,
20390
+ connectedTo: [plan.connectionName, plan.trace.pcb_trace_id]
20071
20391
  };
20072
20392
  }
20073
- function getAllRoutedTraceCopper(srj, allowBlindAndBuriedVias = true) {
20074
- return (srj.traces ?? []).map(
20075
- (trace) => getRoutedTraceCopper(srj, trace, allowBlindAndBuriedVias)
20076
- );
20077
- }
20078
-
20079
- // node_modules/@tscircuit/fanout-solver/lib/match-component-dogbone-via-sites.ts
20080
- var EPSILON5 = 1e-9;
20081
- var DEFAULT_MAXIMUM_SEARCH_STATES = 1e5;
20082
- function assertGeometryRules(rules) {
20083
- for (const [name, value] of [
20084
- ["viaDiameter", rules.viaDiameter],
20085
- ["traceWidth", rules.traceWidth]
20086
- ]) {
20087
- if (!Number.isFinite(value) || value <= 0) {
20393
+ function buildOutputSimpleRouteJson(params) {
20394
+ const { inputSrj, plans, layerNames } = params;
20395
+ const outputConnections = inputSrj.connections.map((connection) => ({
20396
+ ...connection,
20397
+ pointsToConnect: connection.pointsToConnect.map((point6) => ({ ...point6 }))
20398
+ }));
20399
+ const viaObstacles = [];
20400
+ const planeTerminatedConnectionNames = /* @__PURE__ */ new Set();
20401
+ for (const plan of plans) {
20402
+ const connection = outputConnections[plan.connectionIndex];
20403
+ if (!connection) {
20088
20404
  throw new Error(
20089
- `FanoutSolver: dogbone ${name} must be a positive finite number, received ${value}`
20405
+ `FanoutSolver: output connection index ${plan.connectionIndex} is missing`
20090
20406
  );
20091
20407
  }
20092
- }
20093
- if (!Number.isFinite(rules.clearance) || rules.clearance < 0) {
20094
- throw new Error(
20095
- `FanoutSolver: dogbone clearance must be a non-negative finite number, received ${rules.clearance}`
20096
- );
20097
- }
20098
- if (rules.viaHoleDiameter !== void 0 && (!Number.isFinite(rules.viaHoleDiameter) || rules.viaHoleDiameter <= 0)) {
20099
- throw new Error(
20100
- `FanoutSolver: dogbone viaHoleDiameter must be a positive finite number, received ${rules.viaHoleDiameter}`
20101
- );
20102
- }
20103
- if (rules.holeToHoleClearance !== void 0 && (!Number.isFinite(rules.holeToHoleClearance) || rules.holeToHoleClearance < 0)) {
20104
- throw new Error(
20105
- `FanoutSolver: dogbone holeToHoleClearance must be a non-negative finite number, received ${rules.holeToHoleClearance}`
20106
- );
20107
- }
20108
- if (rules.holeToHoleClearance !== void 0 && rules.viaHoleDiameter === void 0) {
20109
- throw new Error(
20110
- "FanoutSolver: dogbone holeToHoleClearance requires viaHoleDiameter"
20111
- );
20112
- }
20113
- const maximumSearchStates = rules.maximumSearchStates ?? DEFAULT_MAXIMUM_SEARCH_STATES;
20114
- if (!Number.isInteger(maximumSearchStates) || maximumSearchStates < 1) {
20115
- throw new Error(
20116
- `FanoutSolver: dogbone maximumSearchStates must be a positive integer, received ${maximumSearchStates}`
20117
- );
20118
- }
20119
- return maximumSearchStates;
20120
- }
20121
- function uniqueSortedCoordinates(values) {
20122
- const result = [];
20123
- for (const value of values.toSorted((first, second) => first - second)) {
20124
- if (!Number.isFinite(value)) continue;
20125
- if (result.length === 0 || Math.abs(result.at(-1) - value) > EPSILON5) {
20126
- result.push(value);
20408
+ const outputIds = createFanoutOutputIds(plan);
20409
+ connection.pointsToConnect[plan.sourcePointIndex] = {
20410
+ x: plan.exitPoint.x,
20411
+ y: plan.exitPoint.y,
20412
+ layer: plan.targetLayer,
20413
+ pointId: plan.termination.type === "plane" ? outputIds.planeExitPointId : outputIds.boundaryExitPointId
20414
+ };
20415
+ if (plan.termination.type === "plane") {
20416
+ planeTerminatedConnectionNames.add(plan.connectionName);
20127
20417
  }
20128
- }
20129
- return result;
20130
- }
20131
- function getComponentMatchingInputs(preparedBuses) {
20132
- const byComponent = /* @__PURE__ */ new Map();
20133
- const componentByConnectionIndex = /* @__PURE__ */ new Map();
20134
- const obstacleSetByComponentId = /* @__PURE__ */ new Map();
20135
- for (const bus of preparedBuses) {
20136
- let component = byComponent.get(bus.componentId);
20137
- if (!component) {
20138
- component = {
20139
- componentId: bus.componentId,
20140
- connections: [],
20418
+ if (plan.via) {
20419
+ const viaObstacle = createViaObstacle(plan, layerNames, plan.via, 0);
20420
+ if (viaObstacle) viaObstacles.push(viaObstacle);
20421
+ }
20422
+ for (const [additionalViaIndex, via] of (plan.additionalVias ?? []).entries()) {
20423
+ const viaObstacle = createViaObstacle(
20424
+ plan,
20425
+ layerNames,
20426
+ via,
20427
+ additionalViaIndex + 1
20428
+ );
20429
+ if (viaObstacle) viaObstacles.push(viaObstacle);
20430
+ }
20431
+ if (plan.planeEndpointVia) {
20432
+ const endpointViaObstacle = createViaObstacle(
20433
+ plan,
20434
+ layerNames,
20435
+ plan.planeEndpointVia,
20436
+ "endpoint"
20437
+ );
20438
+ if (endpointViaObstacle) viaObstacles.push(endpointViaObstacle);
20439
+ }
20440
+ }
20441
+ const planTraces = plans.flatMap((plan) => [
20442
+ plan.trace,
20443
+ ...plan.planeEndpointTrace ? [plan.planeEndpointTrace] : []
20444
+ ]);
20445
+ const coordinateRoutePoints = planTraces.flatMap(
20446
+ (trace) => trace.route.filter(
20447
+ (routePoint) => "x" in routePoint && "y" in routePoint
20448
+ )
20449
+ );
20450
+ const boundsMargin = Math.max(
20451
+ inputSrj.defaultObstacleMargin ?? 0,
20452
+ inputSrj.minTraceWidth
20453
+ );
20454
+ const outputBounds = coordinateRoutePoints.length === 0 ? { ...inputSrj.bounds } : {
20455
+ minX: Math.min(
20456
+ inputSrj.bounds.minX,
20457
+ ...coordinateRoutePoints.map(
20458
+ (routePoint) => routePoint.x - boundsMargin
20459
+ )
20460
+ ),
20461
+ maxX: Math.max(
20462
+ inputSrj.bounds.maxX,
20463
+ ...coordinateRoutePoints.map(
20464
+ (routePoint) => routePoint.x + boundsMargin
20465
+ )
20466
+ ),
20467
+ minY: Math.min(
20468
+ inputSrj.bounds.minY,
20469
+ ...coordinateRoutePoints.map(
20470
+ (routePoint) => routePoint.y - boundsMargin
20471
+ )
20472
+ ),
20473
+ maxY: Math.max(
20474
+ inputSrj.bounds.maxY,
20475
+ ...coordinateRoutePoints.map(
20476
+ (routePoint) => routePoint.y + boundsMargin
20477
+ )
20478
+ )
20479
+ };
20480
+ return {
20481
+ ...inputSrj,
20482
+ fanoutPlaneConnectivity: plans.flatMap(
20483
+ (plan) => plan.termination.type === "plane" ? [
20484
+ {
20485
+ connectionName: plan.connectionName,
20486
+ layer: plan.termination.layer
20487
+ }
20488
+ ] : []
20489
+ ),
20490
+ bounds: outputBounds,
20491
+ connections: outputConnections.filter(
20492
+ (connection) => !planeTerminatedConnectionNames.has(connection.name)
20493
+ ),
20494
+ buses: inputSrj.buses?.map((bus) => ({
20495
+ ...bus,
20496
+ connectionNames: bus.connectionNames.filter(
20497
+ (connectionName) => !planeTerminatedConnectionNames.has(connectionName)
20498
+ )
20499
+ })).filter((bus) => bus.connectionNames.length > 0),
20500
+ obstacles: [
20501
+ ...inputSrj.obstacles.map((obstacle) => ({
20502
+ ...obstacle,
20503
+ center: { ...obstacle.center },
20504
+ layers: [...obstacle.layers],
20505
+ connectedTo: [...obstacle.connectedTo]
20506
+ })),
20507
+ ...viaObstacles
20508
+ ],
20509
+ traces: [
20510
+ ...(inputSrj.traces ?? []).map((trace) => ({
20511
+ ...trace,
20512
+ route: trace.route.map((routePoint) => ({ ...routePoint }))
20513
+ })),
20514
+ ...planTraces
20515
+ ]
20516
+ };
20517
+ }
20518
+
20519
+ // node_modules/@tscircuit/fanout-solver/lib/get-routed-trace-copper.ts
20520
+ function getRoutedTraceCopper(srj, trace, allowBlindAndBuriedVias = true) {
20521
+ const layerNames = getCopperLayerNames(srj.layerCount);
20522
+ const segments = [];
20523
+ const vias = [];
20524
+ let previousWire;
20525
+ for (const routePoint of trace.route) {
20526
+ if (routePoint.route_type === "via") {
20527
+ vias.push({
20528
+ center: { x: routePoint.x, y: routePoint.y },
20529
+ diameter: routePoint.via_diameter ?? srj.minViaPadDiameter ?? srj.min_via_pad_diameter ?? srj.minViaDiameter ?? srj.minTraceWidth,
20530
+ spanLayers: getRouteViaSpanLayers({
20531
+ fromLayer: routePoint.from_layer,
20532
+ toLayer: routePoint.to_layer,
20533
+ layers: "layers" in routePoint && Array.isArray(routePoint.layers) ? routePoint.layers : void 0,
20534
+ layerNames,
20535
+ allowBlindAndBuriedVias
20536
+ })
20537
+ });
20538
+ previousWire = void 0;
20539
+ continue;
20540
+ }
20541
+ if (routePoint.route_type !== "wire") {
20542
+ previousWire = void 0;
20543
+ continue;
20544
+ }
20545
+ if (previousWire?.layer === routePoint.layer && (previousWire.x !== routePoint.x || previousWire.y !== routePoint.y)) {
20546
+ segments.push({
20547
+ start: { x: previousWire.x, y: previousWire.y },
20548
+ end: { x: routePoint.x, y: routePoint.y },
20549
+ width: Math.max(previousWire.width, routePoint.width),
20550
+ layer: routePoint.layer
20551
+ });
20552
+ }
20553
+ previousWire = routePoint;
20554
+ }
20555
+ return {
20556
+ trace,
20557
+ connectionName: trace.connection_name,
20558
+ segments,
20559
+ vias
20560
+ };
20561
+ }
20562
+ function getAllRoutedTraceCopper(srj, allowBlindAndBuriedVias = true) {
20563
+ return (srj.traces ?? []).map(
20564
+ (trace) => getRoutedTraceCopper(srj, trace, allowBlindAndBuriedVias)
20565
+ );
20566
+ }
20567
+
20568
+ // node_modules/@tscircuit/fanout-solver/lib/get-dogbone-side-variants.ts
20569
+ function getDogboneSideVariants(connections, direction) {
20570
+ const axis = direction === "up" || direction === "down" ? "y" : "x";
20571
+ const variants = connections.slice(0, 32).map(({ connectionIndex }) => [connectionIndex]);
20572
+ for (let first = 0; first < connections.length; first++) {
20573
+ for (let second = first + 1; second < connections.length; second++) {
20574
+ if (variants.length >= 32) return variants;
20575
+ const a = connections[first];
20576
+ const b = connections[second];
20577
+ if (Math.abs(a.sourcePoint[axis] - b.sourcePoint[axis]) <= 1e-9) {
20578
+ variants.push([a.connectionIndex, b.connectionIndex]);
20579
+ }
20580
+ }
20581
+ }
20582
+ return variants;
20583
+ }
20584
+
20585
+ // node_modules/@tscircuit/fanout-solver/lib/match-component-dogbone-via-sites.ts
20586
+ var EPSILON6 = 1e-9;
20587
+ var DEFAULT_MAXIMUM_SEARCH_STATES = 1e5;
20588
+ function assertGeometryRules(rules) {
20589
+ for (const [name, value] of [
20590
+ ["viaDiameter", rules.viaDiameter],
20591
+ ["traceWidth", rules.traceWidth]
20592
+ ]) {
20593
+ if (!Number.isFinite(value) || value <= 0) {
20594
+ throw new Error(
20595
+ `FanoutSolver: dogbone ${name} must be a positive finite number, received ${value}`
20596
+ );
20597
+ }
20598
+ }
20599
+ if (!Number.isFinite(rules.clearance) || rules.clearance < 0) {
20600
+ throw new Error(
20601
+ `FanoutSolver: dogbone clearance must be a non-negative finite number, received ${rules.clearance}`
20602
+ );
20603
+ }
20604
+ if (rules.viaHoleDiameter !== void 0 && (!Number.isFinite(rules.viaHoleDiameter) || rules.viaHoleDiameter <= 0)) {
20605
+ throw new Error(
20606
+ `FanoutSolver: dogbone viaHoleDiameter must be a positive finite number, received ${rules.viaHoleDiameter}`
20607
+ );
20608
+ }
20609
+ if (rules.holeToHoleClearance !== void 0 && (!Number.isFinite(rules.holeToHoleClearance) || rules.holeToHoleClearance < 0)) {
20610
+ throw new Error(
20611
+ `FanoutSolver: dogbone holeToHoleClearance must be a non-negative finite number, received ${rules.holeToHoleClearance}`
20612
+ );
20613
+ }
20614
+ if (rules.holeToHoleClearance !== void 0 && rules.viaHoleDiameter === void 0) {
20615
+ throw new Error(
20616
+ "FanoutSolver: dogbone holeToHoleClearance requires viaHoleDiameter"
20617
+ );
20618
+ }
20619
+ const maximumSearchStates = rules.maximumSearchStates ?? DEFAULT_MAXIMUM_SEARCH_STATES;
20620
+ if (!Number.isInteger(maximumSearchStates) || maximumSearchStates < 1) {
20621
+ throw new Error(
20622
+ `FanoutSolver: dogbone maximumSearchStates must be a positive integer, received ${maximumSearchStates}`
20623
+ );
20624
+ }
20625
+ return maximumSearchStates;
20626
+ }
20627
+ function uniqueSortedCoordinates(values) {
20628
+ const result = [];
20629
+ for (const value of values.toSorted((first, second) => first - second)) {
20630
+ if (!Number.isFinite(value)) continue;
20631
+ if (result.length === 0 || Math.abs(result.at(-1) - value) > EPSILON6) {
20632
+ result.push(value);
20633
+ }
20634
+ }
20635
+ return result;
20636
+ }
20637
+ function getComponentMatchingInputs(preparedBuses) {
20638
+ const byComponent = /* @__PURE__ */ new Map();
20639
+ const componentByConnectionIndex = /* @__PURE__ */ new Map();
20640
+ const obstacleSetByComponentId = /* @__PURE__ */ new Map();
20641
+ for (const bus of preparedBuses) {
20642
+ let component = byComponent.get(bus.componentId);
20643
+ if (!component) {
20644
+ component = {
20645
+ componentId: bus.componentId,
20646
+ connections: [],
20141
20647
  obstacles: [],
20142
20648
  xCoordinates: [],
20143
20649
  yCoordinates: [],
@@ -20149,10 +20655,10 @@ function getComponentMatchingInputs(preparedBuses) {
20149
20655
  }
20150
20656
  component.xCoordinates.push(...bus.xCoordinates);
20151
20657
  component.yCoordinates.push(...bus.yCoordinates);
20152
- if (Number.isFinite(bus.pitchX) && bus.pitchX > EPSILON5) {
20658
+ if (Number.isFinite(bus.pitchX) && bus.pitchX > EPSILON6) {
20153
20659
  component.pitchX = Math.min(component.pitchX, bus.pitchX);
20154
20660
  }
20155
- if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON5) {
20661
+ if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON6) {
20156
20662
  component.pitchY = Math.min(component.pitchY, bus.pitchY);
20157
20663
  }
20158
20664
  const obstacleSet = obstacleSetByComponentId.get(bus.componentId);
@@ -20199,7 +20705,7 @@ function getComponentMatchingInputs(preparedBuses) {
20199
20705
  }
20200
20706
  function getInterstitialCoordinates(params) {
20201
20707
  const { coordinates, pitch } = params;
20202
- if (coordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON5) {
20708
+ if (coordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON6) {
20203
20709
  return [];
20204
20710
  }
20205
20711
  const interstitialCoordinates = [coordinates[0] - pitch / 2];
@@ -20217,9 +20723,9 @@ function getAdjacentInterstitialCoordinates(params) {
20217
20723
  coordinates,
20218
20724
  pitch
20219
20725
  });
20220
- const before = interstitialCoordinates.filter((coordinate) => coordinate < sourceCoordinate - EPSILON5).at(-1);
20726
+ const before = interstitialCoordinates.filter((coordinate) => coordinate < sourceCoordinate - EPSILON6).at(-1);
20221
20727
  const after = interstitialCoordinates.find(
20222
- (coordinate) => coordinate > sourceCoordinate + EPSILON5
20728
+ (coordinate) => coordinate > sourceCoordinate + EPSILON6
20223
20729
  );
20224
20730
  return [before, after].filter(
20225
20731
  (coordinate) => coordinate !== void 0
@@ -20228,25 +20734,25 @@ function getAdjacentInterstitialCoordinates(params) {
20228
20734
  function directSegmentIsStraightOr45(start, end) {
20229
20735
  const absoluteX = Math.abs(end.x - start.x);
20230
20736
  const absoluteY = Math.abs(end.y - start.y);
20231
- return absoluteX <= EPSILON5 || absoluteY <= EPSILON5 || Math.abs(absoluteX - absoluteY) <= EPSILON5;
20737
+ return absoluteX <= EPSILON6 || absoluteY <= EPSILON6 || Math.abs(absoluteX - absoluteY) <= EPSILON6;
20232
20738
  }
20233
20739
  function getOutwardRank(params) {
20234
20740
  const { source, site, direction } = params;
20235
20741
  const outwardDisplacement = direction === "right" ? site.x - source.x : direction === "left" ? source.x - site.x : direction === "up" ? site.y - source.y : source.y - site.y;
20236
- return outwardDisplacement > EPSILON5 ? 0 : Math.abs(outwardDisplacement) <= EPSILON5 ? 1 : 2;
20742
+ return outwardDisplacement > EPSILON6 ? 0 : Math.abs(outwardDisplacement) <= EPSILON6 ? 1 : 2;
20237
20743
  }
20238
20744
  function viaSiteClearsObstacles(params) {
20239
20745
  const { point: point6, obstacles, viaDiameter, clearance } = params;
20240
20746
  const requiredClearance = viaDiameter / 2 + clearance;
20241
20747
  return obstacles.every(
20242
- (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredClearance - EPSILON5
20748
+ (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredClearance - EPSILON6
20243
20749
  );
20244
20750
  }
20245
20751
  function sourceSegmentClearsOtherObstacles(params) {
20246
20752
  const { segment: segment2, sourceObstacle, obstacles, clearance } = params;
20247
20753
  const requiredClearance = segment2.width / 2 + clearance;
20248
20754
  return obstacles.every(
20249
- (obstacle) => obstacle === sourceObstacle || distanceSegmentToObstacle(segment2, obstacle) >= requiredClearance - EPSILON5
20755
+ (obstacle) => obstacle === sourceObstacle || distanceSegmentToObstacle(segment2, obstacle) >= requiredClearance - EPSILON6
20250
20756
  );
20251
20757
  }
20252
20758
  function getConnectionCandidates(params) {
@@ -20274,7 +20780,7 @@ function getConnectionCandidates(params) {
20274
20780
  ];
20275
20781
  const uniquePoints2 = [];
20276
20782
  for (const point6 of rawPoints) {
20277
- if (!uniquePoints2.some((candidate) => distance9(candidate, point6) <= EPSILON5)) {
20783
+ if (!uniquePoints2.some((candidate) => distance9(candidate, point6) <= EPSILON6)) {
20278
20784
  uniquePoints2.push(point6);
20279
20785
  }
20280
20786
  }
@@ -20325,7 +20831,7 @@ function getConnectionCandidates(params) {
20325
20831
  point6,
20326
20832
  blocker.segment.start,
20327
20833
  blocker.segment.end
20328
- ) < viaToTraceClearance - EPSILON5) {
20834
+ ) < viaToTraceClearance - EPSILON6) {
20329
20835
  return false;
20330
20836
  }
20331
20837
  return blocker.segment.layer !== sourceSegment.layer || segmentsAreClear(sourceSegment, blocker.segment, rules.clearance);
@@ -20337,14 +20843,14 @@ function getConnectionCandidates(params) {
20337
20843
  if (blocker.connectionIndex === preparedConnection.connectionIndex) {
20338
20844
  return true;
20339
20845
  }
20340
- if (distance9(point6, blocker.center) < (rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON5) {
20846
+ if (distance9(point6, blocker.center) < (rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON6) {
20341
20847
  return false;
20342
20848
  }
20343
20849
  if (blocker.spanLayers.includes(sourceSegment.layer) && distancePointToSegment(
20344
20850
  blocker.center,
20345
20851
  sourceSegment.start,
20346
20852
  sourceSegment.end
20347
- ) < blocker.diameter / 2 + sourceSegment.width / 2 + rules.clearance - EPSILON5) {
20853
+ ) < blocker.diameter / 2 + sourceSegment.width / 2 + rules.clearance - EPSILON6) {
20348
20854
  return false;
20349
20855
  }
20350
20856
  return true;
@@ -20361,7 +20867,7 @@ function getConnectionCandidates(params) {
20361
20867
  const perpendicularCoordinates = direction === "left" || direction === "right" ? component.yCoordinates : component.xCoordinates;
20362
20868
  const sourcePerpendicularCoordinate = direction === "left" || direction === "right" ? source.y : source.x;
20363
20869
  const perpendicularGridIndex = perpendicularCoordinates.findIndex(
20364
- (coordinate) => Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON5
20870
+ (coordinate) => Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON6
20365
20871
  );
20366
20872
  const planeCheckerboardSide = perpendicularGridIndex >= 0 ? perpendicularGridIndex % 2 === 0 ? 1 : -1 : void 0;
20367
20873
  const preferredPerpendicularSide = connection.terminationType === "boundary" ? rules.preferredBoundaryPerpendicularSideByBusId?.get(connection.busId) : rules.preferPlaneCheckerboardSites && connection.terminationType === "plane" && (direction === "left" || direction === "right") ? planeCheckerboardSide : void 0;
@@ -20372,10 +20878,10 @@ function getConnectionCandidates(params) {
20372
20878
  const getPerpendicularPreferenceRank = (candidate) => {
20373
20879
  if (preferredPerpendicularSide === void 0) return 0;
20374
20880
  const displacement = direction === "left" || direction === "right" ? candidate.point.y - source.y : candidate.point.x - source.x;
20375
- return displacement * preferredPerpendicularSide > EPSILON5 ? 0 : Math.abs(displacement) <= EPSILON5 ? 1 : 2;
20881
+ return displacement * preferredPerpendicularSide > EPSILON6 ? 0 : Math.abs(displacement) <= EPSILON6 ? 1 : 2;
20376
20882
  };
20377
20883
  return candidates.toSorted(
20378
- (first, second) => (preferredViaPoint ? Number(distance9(first.point, preferredViaPoint) > EPSILON5) - Number(distance9(second.point, preferredViaPoint) > EPSILON5) : 0) || (preferOutward ? first.outwardRank - second.outwardRank : second.outwardRank - first.outwardRank) || getPerpendicularPreferenceRank(first) - getPerpendicularPreferenceRank(second) || distance9(source, first.point) - distance9(source, second.point) || first.point.x - second.point.x || first.point.y - second.point.y
20884
+ (first, second) => (preferredViaPoint ? Number(distance9(first.point, preferredViaPoint) > EPSILON6) - Number(distance9(second.point, preferredViaPoint) > EPSILON6) : 0) || (preferOutward ? first.outwardRank - second.outwardRank : second.outwardRank - first.outwardRank) || getPerpendicularPreferenceRank(first) - getPerpendicularPreferenceRank(second) || distance9(source, first.point) - distance9(source, second.point) || first.point.x - second.point.x || first.point.y - second.point.y
20379
20885
  );
20380
20886
  }
20381
20887
  function candidatesAreMutuallyClear(params) {
@@ -20383,7 +20889,7 @@ function candidatesAreMutuallyClear(params) {
20383
20889
  const canShareCopper = rules.canShareCopper?.(first.connectionIndex, second.connectionIndex) ?? false;
20384
20890
  const requiredHoleSeparation = rules.viaHoleDiameter ? rules.viaHoleDiameter + (rules.holeToHoleClearance ?? rules.clearance) : 0;
20385
20891
  const requiredViaSeparation = canShareCopper ? requiredHoleSeparation : Math.max(rules.viaDiameter + rules.clearance, requiredHoleSeparation);
20386
- if (distance9(first.point, second.point) < requiredViaSeparation - EPSILON5) {
20892
+ if (distance9(first.point, second.point) < requiredViaSeparation - EPSILON6) {
20387
20893
  return false;
20388
20894
  }
20389
20895
  const requiredViaToTraceClearance = rules.viaDiameter / 2 + rules.traceWidth / 2 + rules.clearance;
@@ -20392,11 +20898,11 @@ function candidatesAreMutuallyClear(params) {
20392
20898
  first.point,
20393
20899
  second.sourceSegment.start,
20394
20900
  second.sourceSegment.end
20395
- ) < requiredViaToTraceClearance - EPSILON5 || distancePointToSegment(
20901
+ ) < requiredViaToTraceClearance - EPSILON6 || distancePointToSegment(
20396
20902
  second.point,
20397
20903
  first.sourceSegment.start,
20398
20904
  first.sourceSegment.end
20399
- ) < requiredViaToTraceClearance - EPSILON5) {
20905
+ ) < requiredViaToTraceClearance - EPSILON6) {
20400
20906
  return false;
20401
20907
  }
20402
20908
  }
@@ -20538,7 +21044,7 @@ function getComponentDogboneViaSiteCandidates(preparedBuses, rules) {
20538
21044
  }
20539
21045
 
20540
21046
  // node_modules/@tscircuit/fanout-solver/lib/route-via-minimal-winding.ts
20541
- var EPSILON6 = 1e-7;
21047
+ var EPSILON7 = 1e-7;
20542
21048
  var MAX_GRID_NODE_COUNT = 12e4;
20543
21049
  var MAX_EXPANDED_STATE_COUNT = 24e4;
20544
21050
  var EXPANDED_STATES_PER_STEP = 5e3;
@@ -20694,7 +21200,7 @@ function getConnectorVariants(start, end) {
20694
21200
  const deltaY = end.y - start.y;
20695
21201
  const absoluteX = Math.abs(deltaX);
20696
21202
  const absoluteY = Math.abs(deltaY);
20697
- if (absoluteX < EPSILON6 || absoluteY < EPSILON6 || Math.abs(absoluteX - absoluteY) < EPSILON6) {
21203
+ if (absoluteX < EPSILON7 || absoluteY < EPSILON7 || Math.abs(absoluteX - absoluteY) < EPSILON7) {
20698
21204
  return [[start, end]];
20699
21205
  }
20700
21206
  if (absoluteX > absoluteY) {
@@ -20729,18 +21235,18 @@ function compressPath(points) {
20729
21235
  function getSegments(points, width, layer) {
20730
21236
  return points.slice(1).flatMap((point6, index) => {
20731
21237
  const start = points[index];
20732
- return distance9(start, point6) < EPSILON6 ? [] : [{ start, end: point6, width, layer }];
21238
+ return distance9(start, point6) < EPSILON7 ? [] : [{ start, end: point6, width, layer }];
20733
21239
  });
20734
21240
  }
20735
21241
  function segmentIsStraightOr45Degrees(segment2) {
20736
21242
  const deltaX = Math.abs(segment2.end.x - segment2.start.x);
20737
21243
  const deltaY = Math.abs(segment2.end.y - segment2.start.y);
20738
- return deltaX < EPSILON6 || deltaY < EPSILON6 || Math.abs(deltaX - deltaY) < EPSILON6;
21244
+ return deltaX < EPSILON7 || deltaY < EPSILON7 || Math.abs(deltaX - deltaY) < EPSILON7;
20739
21245
  }
20740
21246
  function pathHasNoProperSelfCrossing(segments) {
20741
21247
  for (let firstIndex = 0; firstIndex < segments.length; firstIndex++) {
20742
21248
  for (let secondIndex = firstIndex + 2; secondIndex < segments.length; secondIndex++) {
20743
- if (firstIndex === 0 && secondIndex === segments.length - 1 && distance9(segments[firstIndex].start, segments[secondIndex].end) < EPSILON6) {
21249
+ if (firstIndex === 0 && secondIndex === segments.length - 1 && distance9(segments[firstIndex].start, segments[secondIndex].end) < EPSILON7) {
20744
21250
  continue;
20745
21251
  }
20746
21252
  if (distanceSegmentToSegment(
@@ -20748,7 +21254,7 @@ function pathHasNoProperSelfCrossing(segments) {
20748
21254
  segments[firstIndex].end,
20749
21255
  segments[secondIndex].start,
20750
21256
  segments[secondIndex].end
20751
- ) < EPSILON6) {
21257
+ ) < EPSILON7) {
20752
21258
  return false;
20753
21259
  }
20754
21260
  }
@@ -20824,7 +21330,7 @@ function buildPlan(params) {
20824
21330
  width: traceWidth,
20825
21331
  layer: connection.sourceLayer
20826
21332
  };
20827
- const hasSourceDogbone = distance9(sourcePoint, terminal.viaPoint) > EPSILON6;
21333
+ const hasSourceDogbone = distance9(sourcePoint, terminal.viaPoint) > EPSILON7;
20828
21334
  const changesLayer = connection.sourceLayer !== targetLayer;
20829
21335
  const targetSegments = getSegments(targetLayerPoints, traceWidth, targetLayer);
20830
21336
  const spanLayers = getViaSpanLayers2({
@@ -20948,7 +21454,8 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
20948
21454
  preferTargetDirectedLaneBias = false,
20949
21455
  allowSourceLayerRouting = false,
20950
21456
  adaptiveRouteOrder = false,
20951
- alignGridToPads = false
21457
+ alignGridToPads = false,
21458
+ includeReverseTargetRotation = false
20952
21459
  } = params;
20953
21460
  if (maximumRouteOrderAttempts !== void 0 && (!Number.isInteger(maximumRouteOrderAttempts) || maximumRouteOrderAttempts < 1)) {
20954
21461
  throw new Error(
@@ -21009,6 +21516,18 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21009
21516
  const margin = (segment2.width + traceWidth) / 2 + clearance;
21010
21517
  return !(Math.max(segment2.start.x, segment2.end.x) < minX - margin || Math.min(segment2.start.x, segment2.end.x) > maxX + margin || Math.max(segment2.start.y, segment2.end.y) < minY - margin || Math.min(segment2.start.y, segment2.end.y) > maxY + margin);
21011
21518
  });
21519
+ if (allowSourceLayerRouting) {
21520
+ blockingSegments.push(
21521
+ ...reservedVias.flatMap(
21522
+ (reserved) => reserved.sourceEscapeSegment?.layer === targetLayer ? [
21523
+ {
21524
+ connectionName: reserved.connectionName,
21525
+ segment: reserved.sourceEscapeSegment
21526
+ }
21527
+ ] : []
21528
+ )
21529
+ );
21530
+ }
21012
21531
  const blockingVias = blockingCopper.vias.filter(({ via }) => {
21013
21532
  if (!via.spanLayers.includes(targetLayer)) return false;
21014
21533
  const margin = via.diameter / 2 + traceWidth / 2 + clearance;
@@ -21065,7 +21584,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21065
21584
  if (obstacle.connectedTo.includes(connectionName) || allowSameNetMerges && obstacleSharesElectricalNet(srj, obstacle, connectionName)) {
21066
21585
  continue;
21067
21586
  }
21068
- if (distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleClearance - EPSILON6) {
21587
+ if (distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleClearance - EPSILON7) {
21069
21588
  return false;
21070
21589
  }
21071
21590
  }
@@ -21076,7 +21595,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21076
21595
  segment2.end,
21077
21596
  blocker.segment.start,
21078
21597
  blocker.segment.end
21079
- ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON6) {
21598
+ ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON7) {
21080
21599
  return false;
21081
21600
  }
21082
21601
  }
@@ -21090,7 +21609,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21090
21609
  segment2.end,
21091
21610
  blocker.segment.start,
21092
21611
  blocker.segment.end
21093
- ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON6) {
21612
+ ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON7) {
21094
21613
  return false;
21095
21614
  }
21096
21615
  }
@@ -21110,7 +21629,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21110
21629
  if (blocker.via.center.x < segmentMinX - requiredDistance || blocker.via.center.x > segmentMaxX + requiredDistance || blocker.via.center.y < segmentMinY - requiredDistance || blocker.via.center.y > segmentMaxY + requiredDistance) {
21111
21630
  continue;
21112
21631
  }
21113
- if (distancePointToSegment(blocker.via.center, segment2.start, segment2.end) < requiredDistance - EPSILON6) {
21632
+ if (distancePointToSegment(blocker.via.center, segment2.start, segment2.end) < requiredDistance - EPSILON7) {
21114
21633
  return false;
21115
21634
  }
21116
21635
  }
@@ -21308,10 +21827,10 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21308
21827
  let expandedBatchPoints = [];
21309
21828
  while (heap.size > 0 && expandedStateCount < MAX_EXPANDED_STATE_COUNT) {
21310
21829
  const current = heap.pop();
21311
- if (current.score >= bestGoalCost - EPSILON6) break;
21830
+ if (current.score >= bestGoalCost - EPSILON7) break;
21312
21831
  const state = current.node * 9 + current.direction;
21313
21832
  const currentDistance = distances[state];
21314
- if (current.score > currentDistance + heuristic(nodes[current.node].point) + EPSILON6)
21833
+ if (current.score > currentDistance + heuristic(nodes[current.node].point) + EPSILON7)
21315
21834
  continue;
21316
21835
  expandedStateCount++;
21317
21836
  expandedStatesSinceYield++;
@@ -21339,7 +21858,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21339
21858
  for (const startConnector of startConnectors) {
21340
21859
  for (const endConnector of endConnectors) {
21341
21860
  const candidateCost = currentDistance - shortestStartLength + startConnector.length + endConnector.length;
21342
- if (candidateCost >= bestGoalCost - EPSILON6) continue;
21861
+ if (candidateCost >= bestGoalCost - EPSILON7) continue;
21343
21862
  const points = compressPath([
21344
21863
  ...startConnector.points,
21345
21864
  ...gridPoints.slice(1),
@@ -21385,7 +21904,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21385
21904
  const lanePenalty = laneBias === 0 ? 0 : laneBias > 0 ? Math.max(0, targetTrack - nextTrack) * 0.2 : Math.max(0, nextTrack - targetTrack) * 0.2;
21386
21905
  const nextDistance = currentDistance + (deltaColumn !== 0 && deltaRow !== 0 ? gridStep * Math.SQRT2 : gridStep) + (addsTurn ? gridStep * 0.2 : 0) + lanePenalty;
21387
21906
  const nextState = nextNode * 9 + directionIndex;
21388
- if (nextDistance >= distances[nextState] - EPSILON6) continue;
21907
+ if (nextDistance >= distances[nextState] - EPSILON7) continue;
21389
21908
  const edgeIndex = current.node * 8 + directionIndex;
21390
21909
  if (edgeClearance[edgeIndex] === 0) {
21391
21910
  const clear = segmentIsClear({
@@ -21448,9 +21967,9 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21448
21967
  );
21449
21968
  const meanViaTrack = viaTracks.reduce((sum, track) => sum + track, 0) / viaTracks.length;
21450
21969
  const meanTargetTrack = targetTracks.reduce((sum, track) => sum + track, 0) / targetTracks.length;
21451
- const viasAreBeforeTargets = Math.max(...viaTracks) < Math.min(...targetTracks) - EPSILON6;
21452
- const viasAreAfterTargets = Math.min(...viaTracks) > Math.max(...targetTracks) + EPSILON6;
21453
- const laneBiases = preferTargetDirectedLaneBias ? viasAreBeforeTargets ? [0, 1, -1] : viasAreAfterTargets ? [0, -1, 1] : bus.direction === boundaryDirection && meanTargetTrack > meanViaTrack + EPSILON6 ? [1, 0, -1] : bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON6 ? [-1, 0, 1] : [0, 1, -1] : viasAreBeforeTargets ? [1, 0, -1] : viasAreAfterTargets ? [-1, 0, 1] : [0, 1, -1];
21970
+ const viasAreBeforeTargets = Math.max(...viaTracks) < Math.min(...targetTracks) - EPSILON7;
21971
+ const viasAreAfterTargets = Math.min(...viaTracks) > Math.max(...targetTracks) + EPSILON7;
21972
+ const laneBiases = preferTargetDirectedLaneBias ? viasAreBeforeTargets ? [0, 1, -1] : viasAreAfterTargets ? [0, -1, 1] : bus.direction === boundaryDirection && meanTargetTrack > meanViaTrack + EPSILON7 ? [1, 0, -1] : bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON7 ? [-1, 0, 1] : [0, 1, -1] : viasAreBeforeTargets ? [1, 0, -1] : viasAreAfterTargets ? [-1, 0, 1] : [0, 1, -1];
21454
21973
  const initialRouteOrderFactories = [];
21455
21974
  if (alignGridToPads && preferTargetDirectedLaneBias && viasAreBeforeTargets) {
21456
21975
  initialRouteOrderFactories.push(() => targetOrderedTerminals);
@@ -21462,9 +21981,15 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21462
21981
  ]);
21463
21982
  } else if (viasAreAfterTargets) {
21464
21983
  initialRouteOrderFactories.push(() => [...targetOrderedTerminals].reverse());
21984
+ if (includeReverseTargetRotation && targetOrderedTerminals.length > 2) {
21985
+ initialRouteOrderFactories.push(() => [
21986
+ ...targetOrderedTerminals.slice(0, -1).toReversed(),
21987
+ targetOrderedTerminals.at(-1)
21988
+ ]);
21989
+ }
21465
21990
  }
21466
21991
  initialRouteOrderFactories.push(
21467
- ...preferTargetDirectedLaneBias && bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON6 && !viasAreBeforeTargets && !viasAreAfterTargets ? [
21992
+ ...preferTargetDirectedLaneBias && bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON7 && !viasAreBeforeTargets && !viasAreAfterTargets ? [
21468
21993
  () => [...targetOrderedTerminals].reverse(),
21469
21994
  () => targetOrderedTerminals
21470
21995
  ] : [
@@ -23037,7 +23562,7 @@ function routePlaneTerminatedBus(params) {
23037
23562
  otherPlans: [...acceptedPlans, ...fixedPlans],
23038
23563
  staticClearanceCache,
23039
23564
  blockingBusCounts,
23040
- cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${candidateIndex}`,
23565
+ cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${fixedViaPoint.x}:${fixedViaPoint.y}:${candidateIndex}`,
23041
23566
  srj,
23042
23567
  sharedBoundary: bus.sharedBoundary,
23043
23568
  clearance,
@@ -23598,6 +24123,36 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23598
24123
  const dogboneTerminalPatterns = acceptedBoundaryPlansExist && !allowBlindAndBuriedVias ? [...mixedDogboneTerminalPatterns, ...uniformDogboneTerminalPatterns] : [...uniformDogboneTerminalPatterns, ...mixedDogboneTerminalPatterns];
23599
24124
  const unreservedTerminalPatterns = canUseViaInPadTerminals ? planeTerminationsAlreadyOccupyTheFanout ? [viaInPadTerminalPattern, ...dogboneTerminalPatterns] : [...dogboneTerminalPatterns, viaInPadTerminalPattern] : dogboneTerminalPatterns;
23600
24125
  const terminalPatterns = fixedViaPointsByConnectionIndex ? fixedViaTerminalPatterns : [...fixedViaTerminalPatterns, ...unreservedTerminalPatterns];
24126
+ if (!fixedViaPointsByConnectionIndex && !allowBlindAndBuriedVias && bus.connections.length > 1 && bus.connections.every(
24127
+ (connection) => connection.sourceLayer !== targetLayer
24128
+ ) && viaHandednesses.includes(-1) && viaHandednesses.includes(1)) {
24129
+ const variants = getDogboneSideVariants(bus.connections, bus.direction);
24130
+ for (const handedness of viaHandednesses) {
24131
+ for (const flippedIndices of variants) {
24132
+ terminalPatterns.push({
24133
+ label: `local-dogbone-repair-${handedness}-${flippedIndices.join("-")}`,
24134
+ useViaInPad: false,
24135
+ getViaHandedness: () => handedness,
24136
+ maximumRouteOrderAttempts: 6,
24137
+ localDogboneRepair: true,
24138
+ getViaPoint: (connection) => {
24139
+ const point6 = getInitialViaPoint({
24140
+ preparedConnection: connection,
24141
+ bus,
24142
+ targetLayer,
24143
+ traceWidth,
24144
+ viaDiameter,
24145
+ clearance,
24146
+ viaHandedness: handedness
24147
+ });
24148
+ if (!flippedIndices.includes(connection.connectionIndex))
24149
+ return point6;
24150
+ return bus.direction === "up" || bus.direction === "down" ? { x: point6.x, y: 2 * connection.sourcePoint.y - point6.y } : { x: 2 * connection.sourcePoint.x - point6.x, y: point6.y };
24151
+ }
24152
+ });
24153
+ }
24154
+ }
24155
+ }
23601
24156
  const seenTerminalSignatures = /* @__PURE__ */ new Set();
23602
24157
  for (const terminalPattern of terminalPatterns) {
23603
24158
  const terminals = bus.connections.map((preparedConnection) => {
@@ -23640,12 +24195,45 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23640
24195
  };
23641
24196
  });
23642
24197
  const alignGridToPads = alignWindingGridToPads || Boolean(terminalPattern.getViaPoint && !fixedViaPointsByConnectionIndex);
24198
+ if (terminalPattern.localDogboneRepair && !matchComponentDogboneViaSites([bus], {
24199
+ viaDiameter,
24200
+ viaHoleDiameter,
24201
+ traceWidth,
24202
+ clearance,
24203
+ additionalObstacles: srj.obstacles,
24204
+ fixedViaPointsByConnectionIndex: new Map(
24205
+ terminals.map((terminal) => [
24206
+ terminal.connection.connectionIndex,
24207
+ terminal.viaPoint
24208
+ ])
24209
+ ),
24210
+ blockingSegments: acceptedPlans.flatMap(
24211
+ (plan) => getPlanSegments(plan).map((segment2) => ({
24212
+ connectionIndex: plan.connectionIndex,
24213
+ segment: segment2
24214
+ }))
24215
+ ),
24216
+ blockingVias: [
24217
+ ...acceptedPlans.flatMap(
24218
+ (plan) => getPlanVias2(plan).map((via) => ({
24219
+ connectionIndex: plan.connectionIndex,
24220
+ center: via.center,
24221
+ diameter: via.diameter,
24222
+ spanLayers: via.spanLayers
24223
+ }))
24224
+ ),
24225
+ // These are future connections, not members of the current bus.
24226
+ ...reservedVias.map(({ via }) => ({ connectionIndex: -1, ...via }))
24227
+ ]
24228
+ })) {
24229
+ continue;
24230
+ }
23643
24231
  const gridStepDivisor = terminalPattern.getViaPoint && Math.min(bus.pitchX, bus.pitchY) - 2 * (viaDiameter / 2 + traceWidth / 2 + clearance) < traceWidth + clearance ? 2 : 1;
23644
24232
  const terminalSignature = `${terminals.map(
23645
24233
  (terminal) => `${terminal.connection.connectionIndex}:${terminal.viaPoint.x}:${terminal.viaPoint.y}:${terminal.exitPoint.x}:${terminal.exitPoint.y}`
23646
24234
  ).join(
23647
24235
  "|"
23648
- )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}`;
24236
+ )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}:${Boolean(terminalPattern.localDogboneRepair)}`;
23649
24237
  if (seenTerminalSignatures.has(terminalSignature)) continue;
23650
24238
  seenTerminalSignatures.add(terminalSignature);
23651
24239
  const windingSteps = routeViaMinimalWindingAlternativesSteps(
@@ -23665,6 +24253,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23665
24253
  maximumRouteOrderAttempts: terminalPattern.maximumRouteOrderAttempts,
23666
24254
  adaptiveRouteOrder: adaptiveWindingRouteOrder,
23667
24255
  alignGridToPads,
24256
+ includeReverseTargetRotation: terminalPattern.localDogboneRepair,
23668
24257
  reservedVias,
23669
24258
  gridStepDivisor,
23670
24259
  preferTargetDirectedLaneBias: terminalPattern.preferTargetDirectedLaneBias
@@ -23714,41 +24303,46 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23714
24303
  }
23715
24304
  }
23716
24305
  }
23717
- if (alternatives.length === 0 && allowBoundarySideViaFallback && fixedViaPointsByConnectionIndex && bus.connections.length === 1 && bus.exitEdge && bus.connections[0].sourceLayer !== targetLayer) {
23718
- const preparedConnection = bus.connections[0];
24306
+ if (alternatives.length === 0 && allowBoundarySideViaFallback && fixedViaPointsByConnectionIndex && bus.connections.length <= 2 && bus.exitEdge && bus.connections.every(
24307
+ (connection) => connection.sourceLayer === bus.connections[0].sourceLayer && connection.sourceLayer !== targetLayer
24308
+ )) {
24309
+ const sourceLayer2 = bus.connections[0].sourceLayer;
23719
24310
  const boundaryDirection = getDirectionForExitEdge(bus.exitEdge);
23720
24311
  const boundaryExitAxis = getExitAxis(bus, boundaryDirection);
23721
- const finalTrack = preferCornerBoundaryVia && getCornerSide(bus) ? getCornerTargetTrack({
23722
- bus,
23723
- connection: preparedConnection,
23724
- cornerExitLaneOffset: cornerLaneOffsets.exit,
23725
- traceWidth,
23726
- viaDiameter,
23727
- clearance,
23728
- layerNames,
23729
- targetLayer,
23730
- cornerBandTargetTrackOffset
23731
- }) : getBoundaryTargetTrack({
23732
- bus,
23733
- connection: preparedConnection,
23734
- boundaryDirection
23735
- });
23736
- const finalExitPoint = makePoint(
23737
- boundaryExitAxis,
23738
- finalTrack,
23739
- boundaryDirection
24312
+ const finalTracks = bus.connections.map(
24313
+ (preparedConnection) => preferCornerBoundaryVia && getCornerSide(bus) ? getCornerTargetTrack({
24314
+ bus,
24315
+ connection: preparedConnection,
24316
+ cornerExitLaneOffset: cornerLaneOffsets.exit,
24317
+ traceWidth,
24318
+ viaDiameter,
24319
+ clearance,
24320
+ layerNames,
24321
+ targetLayer,
24322
+ cornerBandTargetTrackOffset
24323
+ }) : getBoundaryTargetTrack({
24324
+ bus,
24325
+ connection: preparedConnection,
24326
+ boundaryDirection
24327
+ })
24328
+ );
24329
+ const finalExitPoints = finalTracks.map(
24330
+ (track) => makePoint(boundaryExitAxis, track, boundaryDirection)
23740
24331
  );
23741
24332
  const sourceLayerSrj = {
23742
24333
  ...srj,
23743
- obstacles: srj.obstacles.map(
23744
- (obstacle) => obstacle === preparedConnection.sourceObstacle || distance9(obstacle.center, preparedConnection.sourcePoint) <= 1e-7 && obstacle.layers.includes(preparedConnection.sourceLayer) ? {
24334
+ obstacles: srj.obstacles.map((obstacle) => {
24335
+ const connection = bus.connections.find(
24336
+ (connection2) => obstacle === connection2.sourceObstacle || distance9(obstacle.center, connection2.sourcePoint) <= 1e-7 && obstacle.layers.includes(connection2.sourceLayer)
24337
+ );
24338
+ return connection ? {
23745
24339
  ...obstacle,
23746
24340
  connectedTo: [
23747
24341
  ...obstacle.connectedTo,
23748
- preparedConnection.connection.name
24342
+ connection.connection.name
23749
24343
  ]
23750
- } : obstacle
23751
- )
24344
+ } : obstacle;
24345
+ })
23752
24346
  };
23753
24347
  const insetStep = Math.max(
23754
24348
  viaDiameter / 2 + clearance,
@@ -23757,34 +24351,104 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23757
24351
  const cornerSide = preferCornerBoundaryVia ? getCornerSide(bus) : void 0;
23758
24352
  const boundaryViaCandidates = [1, 2, 3, 4, 5].flatMap((multiple) => {
23759
24353
  const inset = multiple * insetStep;
23760
- const straight = makePoint(
23761
- boundaryExitAxis - directionSign(boundaryDirection) * inset,
23762
- finalTrack,
23763
- boundaryDirection
24354
+ const straight = finalTracks.map(
24355
+ (finalTrack) => makePoint(
24356
+ boundaryExitAxis - directionSign(boundaryDirection) * inset,
24357
+ finalTrack,
24358
+ boundaryDirection
24359
+ )
23764
24360
  );
23765
24361
  if (!cornerSide) return [straight];
23766
24362
  return [
23767
- makePoint(
23768
- boundaryExitAxis - directionSign(boundaryDirection) * inset,
23769
- finalTrack + (cornerSide === "maximum" ? inset : -inset),
23770
- boundaryDirection
24363
+ finalTracks.map(
24364
+ (finalTrack) => makePoint(
24365
+ boundaryExitAxis - directionSign(boundaryDirection) * inset,
24366
+ finalTrack + (cornerSide === "maximum" ? inset : -inset),
24367
+ boundaryDirection
24368
+ )
23771
24369
  ),
23772
24370
  straight
23773
24371
  ];
23774
24372
  });
23775
- for (const boundaryViaPoint of boundaryViaCandidates) {
24373
+ const sourceCenter = {
24374
+ x: bus.connections.reduce(
24375
+ (sum, connection) => sum + connection.sourcePoint.x,
24376
+ 0
24377
+ ) / bus.connections.length,
24378
+ y: bus.connections.reduce(
24379
+ (sum, connection) => sum + connection.sourcePoint.y,
24380
+ 0
24381
+ ) / bus.connections.length
24382
+ };
24383
+ const directions = [
24384
+ { x: -1, y: 0, distance: sourceCenter.x - Math.min(...bus.xCoordinates) },
24385
+ { x: 1, y: 0, distance: Math.max(...bus.xCoordinates) - sourceCenter.x },
24386
+ { x: 0, y: -1, distance: sourceCenter.y - Math.min(...bus.yCoordinates) },
24387
+ { x: 0, y: 1, distance: Math.max(...bus.yCoordinates) - sourceCenter.y }
24388
+ ].toSorted((first, second) => first.distance - second.distance);
24389
+ const displacedViaCandidates = bus.connections.length === 2 ? directions.flatMap(
24390
+ (direction) => [3].map(
24391
+ (multiple) => bus.connections.map((connection) => {
24392
+ const original = fixedViaPointsByConnectionIndex.get(
24393
+ connection.connectionIndex
24394
+ );
24395
+ return {
24396
+ x: original.x + direction.x * multiple * bus.pitchX,
24397
+ y: original.y + direction.y * multiple * bus.pitchY
24398
+ };
24399
+ })
24400
+ )
24401
+ ) : [];
24402
+ const viaCandidates = [
24403
+ ...displacedViaCandidates.map((points) => ({
24404
+ points,
24405
+ boundarySide: false
24406
+ })),
24407
+ ...boundaryViaCandidates.map((points) => ({
24408
+ points,
24409
+ boundarySide: true
24410
+ }))
24411
+ ];
24412
+ for (const { points: boundaryViaPoints, boundarySide } of viaCandidates) {
24413
+ const boundaryVias = bus.connections.map((connection, index) => ({
24414
+ connectionName: connection.connection.name,
24415
+ via: {
24416
+ center: boundaryViaPoints[index],
24417
+ diameter: viaDiameter,
24418
+ spanLayers: getViaSpanLayers2({
24419
+ fromLayer: sourceLayer2,
24420
+ toLayer: targetLayer,
24421
+ layerNames,
24422
+ allowBlindAndBuriedVias
24423
+ })
24424
+ }
24425
+ }));
24426
+ if (boundaryVias.some(
24427
+ (candidate, index) => boundaryVias.some(
24428
+ (other, otherIndex) => index !== otherIndex && (distance9(candidate.via.center, other.via.center) < viaDiameter + clearance - 1e-9 || boundarySide && distancePointToSegment(
24429
+ candidate.via.center,
24430
+ other.via.center,
24431
+ finalExitPoints[otherIndex]
24432
+ ) < viaDiameter / 2 + traceWidth / 2 + clearance - 1e-9)
24433
+ )
24434
+ ))
24435
+ continue;
24436
+ if (boundaryVias.some(
24437
+ ({ via }) => srj.obstacles.some(
24438
+ (obstacle) => obstacle.layers.some((layer) => via.spanLayers.includes(layer)) && distancePointToObstacle(via.center, obstacle) < via.diameter / 2 + clearance - 1e-9
24439
+ )
24440
+ ))
24441
+ continue;
23776
24442
  const sourceLayerSteps = routeViaMinimalWindingAlternativesSteps(
23777
24443
  {
23778
24444
  srj: sourceLayerSrj,
23779
24445
  bus,
23780
- targetLayer: preparedConnection.sourceLayer,
23781
- terminals: [
23782
- {
23783
- connection: preparedConnection,
23784
- viaPoint: preparedConnection.sourcePoint,
23785
- exitPoint: boundaryViaPoint
23786
- }
23787
- ],
24446
+ targetLayer: sourceLayer2,
24447
+ terminals: bus.connections.map((preparedConnection, index) => ({
24448
+ connection: preparedConnection,
24449
+ viaPoint: preparedConnection.sourcePoint,
24450
+ exitPoint: boundaryViaPoints[index]
24451
+ })),
23788
24452
  acceptedPlans,
23789
24453
  layerNames,
23790
24454
  traceWidth,
@@ -23793,8 +24457,8 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23793
24457
  clearance,
23794
24458
  allowBlindAndBuriedVias,
23795
24459
  allowSameNetMerges,
23796
- maximumRouteOrderAttempts: 3,
23797
- reservedVias,
24460
+ maximumRouteOrderAttempts: bus.connections.length === 1 ? 3 : 6,
24461
+ reservedVias: bus.connections.length > 1 ? [...reservedVias, ...boundaryVias] : reservedVias,
23798
24462
  gridStepDivisor: 2,
23799
24463
  allowSourceLayerRouting: true,
23800
24464
  alignGridToPads: true
@@ -23807,72 +24471,124 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23807
24471
  yield {
23808
24472
  phase: "via-minimal-winding",
23809
24473
  busId: bus.busId,
23810
- targetLayer: preparedConnection.sourceLayer,
24474
+ targetLayer: sourceLayer2,
23811
24475
  winding: sourceLayerResult.value
23812
24476
  };
23813
24477
  sourceLayerResult = sourceLayerSteps.next();
23814
24478
  }
23815
- const sourceLayerPlan = sourceLayerResult.value[0]?.[0];
23816
- if (!sourceLayerPlan) continue;
23817
- const targetSegment = {
23818
- start: boundaryViaPoint,
23819
- end: finalExitPoint,
23820
- width: traceWidth,
23821
- layer: targetLayer
23822
- };
23823
- const via = {
23824
- center: boundaryViaPoint,
23825
- diameter: viaDiameter,
23826
- holeDiameter: viaHoleDiameter,
23827
- fromLayer: preparedConnection.sourceLayer,
23828
- toLayer: targetLayer,
23829
- spanLayers: getViaSpanLayers2({
23830
- fromLayer: preparedConnection.sourceLayer,
23831
- toLayer: targetLayer,
23832
- layerNames,
23833
- allowBlindAndBuriedVias
23834
- })
23835
- };
23836
- const sourceRoute = sourceLayerPlan.trace.route.filter(
23837
- (item) => item.route_type === "wire"
23838
- );
23839
- const plans = [
23840
- {
23841
- ...sourceLayerPlan,
23842
- ...preferCornerBoundaryVia ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length } : {},
23843
- targetLayer,
23844
- exitPoint: finalExitPoint,
23845
- via,
23846
- segments: [...sourceLayerPlan.segments, targetSegment],
23847
- length: sourceLayerPlan.length + distance9(boundaryViaPoint, finalExitPoint),
23848
- trace: {
23849
- ...sourceLayerPlan.trace,
23850
- route: [
23851
- ...sourceRoute,
23852
- {
23853
- route_type: "via",
23854
- ...boundaryViaPoint,
23855
- from_layer: preparedConnection.sourceLayer,
23856
- to_layer: targetLayer,
23857
- via_diameter: viaDiameter,
23858
- via_hole_diameter: viaHoleDiameter
23859
- },
23860
- {
23861
- route_type: "wire",
23862
- ...boundaryViaPoint,
23863
- width: traceWidth,
23864
- layer: targetLayer
23865
- },
23866
- {
23867
- route_type: "wire",
23868
- ...finalExitPoint,
23869
- width: traceWidth,
23870
- layer: targetLayer
23871
- }
23872
- ]
23873
- }
24479
+ const sourceLayerPlans = sourceLayerResult.value[0];
24480
+ if (!sourceLayerPlans || sourceLayerPlans.length !== bus.connections.length)
24481
+ continue;
24482
+ let targetLayerPlans;
24483
+ if (!boundarySide) {
24484
+ const targetSteps = routeViaMinimalWindingAlternativesSteps(
24485
+ {
24486
+ srj,
24487
+ bus,
24488
+ targetLayer,
24489
+ terminals: bus.connections.map((connection, index) => ({
24490
+ connection,
24491
+ viaPoint: boundaryViaPoints[index],
24492
+ exitPoint: finalExitPoints[index]
24493
+ })),
24494
+ acceptedPlans,
24495
+ layerNames,
24496
+ traceWidth,
24497
+ viaDiameter,
24498
+ viaHoleDiameter,
24499
+ clearance,
24500
+ allowBlindAndBuriedVias,
24501
+ allowSameNetMerges,
24502
+ maximumRouteOrderAttempts: 6,
24503
+ reservedVias,
24504
+ gridStepDivisor: 2,
24505
+ alignGridToPads: true
24506
+ },
24507
+ 1,
24508
+ includeVisualization
24509
+ );
24510
+ let targetResult = targetSteps.next();
24511
+ while (!targetResult.done) {
24512
+ yield {
24513
+ phase: "via-minimal-winding",
24514
+ busId: bus.busId,
24515
+ targetLayer,
24516
+ winding: targetResult.value
24517
+ };
24518
+ targetResult = targetSteps.next();
24519
+ }
24520
+ targetLayerPlans = targetResult.value[0];
24521
+ if (!targetLayerPlans) continue;
24522
+ }
24523
+ const plans = sourceLayerPlans.map(
24524
+ (sourceLayerPlan, index) => {
24525
+ const boundaryViaPoint = boundaryViaPoints[index];
24526
+ const finalExitPoint = finalExitPoints[index];
24527
+ const targetSegment = {
24528
+ start: boundaryViaPoint,
24529
+ end: finalExitPoint,
24530
+ width: traceWidth,
24531
+ layer: targetLayer
24532
+ };
24533
+ const targetSegments = targetLayerPlans ? targetLayerPlans[index].segments.filter(
24534
+ (segment2) => segment2.layer === targetLayer
24535
+ ) : [targetSegment];
24536
+ const via = {
24537
+ center: boundaryViaPoint,
24538
+ diameter: viaDiameter,
24539
+ holeDiameter: viaHoleDiameter,
24540
+ fromLayer: sourceLayer2,
24541
+ toLayer: targetLayer,
24542
+ spanLayers: getViaSpanLayers2({
24543
+ fromLayer: sourceLayer2,
24544
+ toLayer: targetLayer,
24545
+ layerNames,
24546
+ allowBlindAndBuriedVias
24547
+ })
24548
+ };
24549
+ const sourceRoute = sourceLayerPlan.trace.route.filter(
24550
+ (item) => item.route_type === "wire"
24551
+ );
24552
+ return {
24553
+ ...sourceLayerPlan,
24554
+ ...preferCornerBoundaryVia || bus.connections.length > 1 ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length } : {},
24555
+ targetLayer,
24556
+ exitPoint: finalExitPoint,
24557
+ via,
24558
+ segments: [...sourceLayerPlan.segments, ...targetSegments],
24559
+ length: sourceLayerPlan.length + targetSegments.reduce(
24560
+ (sum, segment2) => sum + distance9(segment2.start, segment2.end),
24561
+ 0
24562
+ ),
24563
+ trace: {
24564
+ ...sourceLayerPlan.trace,
24565
+ route: [
24566
+ ...sourceRoute,
24567
+ {
24568
+ route_type: "via",
24569
+ ...boundaryViaPoint,
24570
+ from_layer: sourceLayer2,
24571
+ to_layer: targetLayer,
24572
+ via_diameter: viaDiameter,
24573
+ via_hole_diameter: viaHoleDiameter
24574
+ },
24575
+ {
24576
+ route_type: "wire",
24577
+ ...boundaryViaPoint,
24578
+ width: traceWidth,
24579
+ layer: targetLayer
24580
+ },
24581
+ ...targetSegments.map((segment2) => ({
24582
+ route_type: "wire",
24583
+ ...segment2.end,
24584
+ width: traceWidth,
24585
+ layer: targetLayer
24586
+ }))
24587
+ ]
24588
+ }
24589
+ };
23874
24590
  }
23875
- ];
24591
+ );
23876
24592
  const boundaryViaPlansAreClear = fanoutPlansAreClear({
23877
24593
  plans: [...acceptedPlans, ...plans],
23878
24594
  srj,
@@ -23881,23 +24597,28 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23881
24597
  allowBlindAndBuriedVias,
23882
24598
  allowSameNetMerges
23883
24599
  });
23884
- const reservedViasAreClear = reservedVias.every((reserved) => {
23885
- if (reserved.connectionName === preparedConnection.connection.name || allowSameNetMerges && connectionsShareElectricalNet(
23886
- srj,
23887
- reserved.connectionName,
23888
- preparedConnection.connection.name
23889
- ))
23890
- return true;
23891
- if (via.spanLayers.some(
23892
- (layer) => reserved.via.spanLayers.includes(layer)
23893
- ) && distance9(via.center, reserved.via.center) < (via.diameter + reserved.via.diameter) / 2 + clearance - 1e-9)
23894
- return false;
23895
- return !reserved.via.spanLayers.includes(targetLayer) || distancePointToSegment(
23896
- reserved.via.center,
23897
- targetSegment.start,
23898
- targetSegment.end
23899
- ) >= reserved.via.diameter / 2 + traceWidth / 2 + clearance - 1e-9;
23900
- });
24600
+ const reservedViasAreClear = plans.every(
24601
+ (plan) => reservedVias.every((reserved) => {
24602
+ const via = plan.via;
24603
+ if (reserved.connectionName === plan.connectionName || allowSameNetMerges && connectionsShareElectricalNet(
24604
+ srj,
24605
+ reserved.connectionName,
24606
+ plan.connectionName
24607
+ ))
24608
+ return true;
24609
+ if (via.spanLayers.some(
24610
+ (layer) => reserved.via.spanLayers.includes(layer)
24611
+ ) && distance9(via.center, reserved.via.center) < (via.diameter + reserved.via.diameter) / 2 + clearance - 1e-9)
24612
+ return false;
24613
+ return plan.segments.every(
24614
+ (segment2) => !reserved.via.spanLayers.includes(segment2.layer) || distancePointToSegment(
24615
+ reserved.via.center,
24616
+ segment2.start,
24617
+ segment2.end
24618
+ ) >= reserved.via.diameter / 2 + traceWidth / 2 + clearance - 1e-9
24619
+ );
24620
+ })
24621
+ );
23901
24622
  if (boundaryViaPlansAreClear && reservedViasAreClear) {
23902
24623
  addAlternative(plans);
23903
24624
  return alternatives;
@@ -23931,326 +24652,92 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23931
24652
  })
23932
24653
  ].filter(
23933
24654
  (track, index, tracks) => tracks.findIndex((candidate) => Math.abs(candidate - track) < 1e-9) === index
23934
- );
23935
- const trackCandidates = preferredTracks.flatMap(
23936
- (preferredTrack) => getTrackCandidates({
23937
- bus,
23938
- connection: preparedConnection,
23939
- preferredTrack,
23940
- traceWidth,
23941
- clearance
23942
- })
23943
- ).filter(
23944
- (track, index, tracks) => tracks.findIndex(
23945
- (candidate) => Math.abs(candidate.value - track.value) < 1e-9
23946
- ) === index
23947
- );
23948
- for (let trackIndex = 0; trackIndex < trackCandidates.length; trackIndex++) {
23949
- const track = trackCandidates[trackIndex];
23950
- const plan = buildPlan2({
23951
- preparedConnection,
23952
- bus,
23953
- targetLayer,
23954
- track: track.value,
23955
- exitAxis,
23956
- layerNames,
23957
- traceWidth,
23958
- viaDiameter,
23959
- viaHoleDiameter,
23960
- viaHandedness,
23961
- interstitialEscape,
23962
- spreadLaneIndex: Math.min(
23963
- connectionRank,
23964
- bus.connections.length - connectionRank - 1
23965
- ),
23966
- cornerExitLaneOffset: cornerLaneOffsets.exit,
23967
- cornerLocalChannelLaneOffset: cornerLaneOffsets.localChannel,
23968
- cornerBoundaryChannelLaneOffset: cornerLaneOffsets.boundaryChannel,
23969
- clearance,
23970
- terminateAtVia: false,
23971
- allowBlindAndBuriedVias,
23972
- cornerBandTargetTrackOffset
23973
- });
23974
- if (!planIsClear({
23975
- plan,
23976
- otherPlans: [...acceptedPlans, ...candidatePlans],
23977
- staticClearanceCache,
23978
- blockingBusCounts,
23979
- cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}:${bus.exitEdge ?? "legacy"}:${cornerLaneOffsets.exit}:${cornerLaneOffsets.localChannel}:${cornerLaneOffsets.boundaryChannel}:${cornerBandTargetTrackOffset ?? 0}`,
23980
- srj,
23981
- sharedBoundary: bus.sharedBoundary,
23982
- clearance,
23983
- allowBlindAndBuriedVias,
23984
- allowSameNetMerges
23985
- })) {
23986
- continue;
23987
- }
23988
- searchConnectionOrder(
23989
- connectionOrder,
23990
- viaHandedness,
23991
- connectionIndex + 1,
23992
- [...candidatePlans, plan]
23993
- );
23994
- if (alternatives.length >= maxAlternatives) return;
23995
- if (maxAlternatives === 1) return;
23996
- }
23997
- };
23998
- for (const viaHandedness of viaHandednesses) {
23999
- for (const connectionOrder of getConnectionOrders(bus)) {
24000
- searchConnectionOrder(connectionOrder, viaHandedness, 0, []);
24001
- if (alternatives.length >= maxAlternatives) return alternatives;
24002
- }
24003
- }
24004
- return alternatives;
24005
- }
24006
- function routeBusAlternatives(params, maxAlternatives = 1) {
24007
- const steps = routeBusAlternativesSteps(params, maxAlternatives);
24008
- let result = steps.next();
24009
- while (!result.done) result = steps.next();
24010
- return result.value;
24011
- }
24012
- function routeBus(params) {
24013
- return routeBusAlternatives(params, 1)[0] ?? null;
24014
- }
24015
-
24016
- // node_modules/@tscircuit/fanout-solver/lib/shorten-bus-plans.ts
24017
- function shortenBusPlans(params) {
24018
- const { bus } = params;
24019
- let plans = [...params.plans];
24020
- if (bus.maxLengthSkew === void 0) return plans;
24021
- const busPlans = plans.filter((plan) => plan.busId === bus.busId).toSorted((first, second) => second.length - first.length);
24022
- for (const plan of busPlans) {
24023
- const minimumLength = Math.min(
24024
- ...plans.filter((p) => p.busId === bus.busId).map((p) => p.length)
24025
- );
24026
- if (plan.length - minimumLength <= bus.maxLengthSkew) continue;
24027
- if (!plan.via || plan.additionalVias?.length || plan.planeEndpointVia || plan.segments.filter((segment2) => segment2.layer === plan.sourceLayer).length !== 1)
24028
- continue;
24029
- const connection = bus.connections.find(
24030
- (c) => c.connectionIndex === plan.connectionIndex
24031
- );
24032
- for (const alignGridToPads of [true, false]) {
24033
- const candidate = routeViaMinimalWinding({
24034
- ...params,
24035
- bus: {
24036
- ...bus,
24037
- connections: [connection],
24038
- routableEscapeLayers: [plan.targetLayer]
24039
- },
24040
- terminals: [
24041
- { connection, viaPoint: plan.via.center, exitPoint: plan.exitPoint }
24042
- ],
24043
- targetLayer: plan.targetLayer,
24044
- acceptedPlans: plans.filter((p) => p !== plan),
24045
- allowBlindAndBuriedVias: false,
24046
- gridStepDivisor: 2,
24047
- alignGridToPads,
24048
- maximumRouteOrderAttempts: 1
24049
- })?.[0];
24050
- if (!candidate || candidate.length >= plan.length - 1e-6) continue;
24051
- const nextPlans = plans.map((p) => p === plan ? candidate : p);
24052
- if (!fanoutPlansAreClear({
24053
- ...params,
24054
- plans: nextPlans,
24055
- allowBlindAndBuriedVias: false
24056
- }))
24057
- continue;
24058
- plans = nextPlans;
24059
- break;
24060
- }
24061
- }
24062
- return plans;
24063
- }
24064
-
24065
- // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
24066
- import { mergeGraphics } from "graphics-debug";
24067
-
24068
- // node_modules/@tscircuit/fanout-solver/lib/add-via-layer-metadata.ts
24069
- function addViaLayerMetadataToTrace(params) {
24070
- const { trace, layerNames, allowBlindAndBuriedVias } = params;
24071
- return {
24072
- ...trace,
24073
- route: trace.route.map(
24074
- (routePoint) => routePoint.route_type === "via" && !allowBlindAndBuriedVias ? {
24075
- ...routePoint,
24076
- layers: getViaSpanLayers2({
24077
- fromLayer: routePoint.from_layer,
24078
- toLayer: routePoint.to_layer,
24079
- layerNames,
24080
- allowBlindAndBuriedVias
24081
- })
24082
- } : { ...routePoint }
24083
- )
24084
- };
24085
- }
24086
- function addViaLayerMetadataToSrj(params) {
24087
- const { srj, layerNames, allowBlindAndBuriedVias } = params;
24088
- return {
24089
- ...srj,
24090
- traces: (srj.traces ?? []).map(
24091
- (trace) => addViaLayerMetadataToTrace({
24092
- trace,
24093
- layerNames,
24094
- allowBlindAndBuriedVias
24095
- })
24096
- )
24097
- };
24098
- }
24099
-
24100
- // node_modules/@tscircuit/fanout-solver/lib/build-output.ts
24101
- function createViaObstacle(plan, layerNames, via, viaIndex) {
24102
- const zLayers = via.spanLayers.map((layer) => {
24103
- const layerIndex = layerNames.indexOf(layer);
24104
- if (layerIndex < 0) {
24105
- throw new Error(
24106
- `FanoutSolver: via for "${plan.connectionName}" uses unknown layer "${layer}"`
24107
- );
24108
- }
24109
- return layerIndex;
24110
- });
24111
- const outputIds = createFanoutOutputIds(plan);
24112
- return {
24113
- obstacleId: viaIndex === "endpoint" ? outputIds.planeEndpointViaObstacleId : viaIndex === 0 ? outputIds.viaObstacleId : `${outputIds.viaObstacleId}:${viaIndex}`,
24114
- type: "rect",
24115
- center: via.center,
24116
- width: via.diameter,
24117
- height: via.diameter,
24118
- layers: via.spanLayers,
24119
- zLayers,
24120
- __zLayers: zLayers,
24121
- connectedTo: [plan.connectionName, plan.trace.pcb_trace_id]
24122
- };
24123
- }
24124
- function buildOutputSimpleRouteJson(params) {
24125
- const { inputSrj, plans, layerNames } = params;
24126
- const outputConnections = inputSrj.connections.map((connection) => ({
24127
- ...connection,
24128
- pointsToConnect: connection.pointsToConnect.map((point6) => ({ ...point6 }))
24129
- }));
24130
- const viaObstacles = [];
24131
- const planeTerminatedConnectionNames = /* @__PURE__ */ new Set();
24132
- for (const plan of plans) {
24133
- const connection = outputConnections[plan.connectionIndex];
24134
- if (!connection) {
24135
- throw new Error(
24136
- `FanoutSolver: output connection index ${plan.connectionIndex} is missing`
24137
- );
24138
- }
24139
- const outputIds = createFanoutOutputIds(plan);
24140
- connection.pointsToConnect[plan.sourcePointIndex] = {
24141
- x: plan.exitPoint.x,
24142
- y: plan.exitPoint.y,
24143
- layer: plan.targetLayer,
24144
- pointId: plan.termination.type === "plane" ? outputIds.planeExitPointId : outputIds.boundaryExitPointId
24145
- };
24146
- if (plan.termination.type === "plane") {
24147
- planeTerminatedConnectionNames.add(plan.connectionName);
24148
- }
24149
- if (plan.via) {
24150
- const viaObstacle = createViaObstacle(plan, layerNames, plan.via, 0);
24151
- if (viaObstacle) viaObstacles.push(viaObstacle);
24152
- }
24153
- for (const [additionalViaIndex, via] of (plan.additionalVias ?? []).entries()) {
24154
- const viaObstacle = createViaObstacle(
24155
- plan,
24655
+ );
24656
+ const trackCandidates = preferredTracks.flatMap(
24657
+ (preferredTrack) => getTrackCandidates({
24658
+ bus,
24659
+ connection: preparedConnection,
24660
+ preferredTrack,
24661
+ traceWidth,
24662
+ clearance
24663
+ })
24664
+ ).filter(
24665
+ (track, index, tracks) => tracks.findIndex(
24666
+ (candidate) => Math.abs(candidate.value - track.value) < 1e-9
24667
+ ) === index
24668
+ );
24669
+ for (let trackIndex = 0; trackIndex < trackCandidates.length; trackIndex++) {
24670
+ const track = trackCandidates[trackIndex];
24671
+ const plan = buildPlan2({
24672
+ preparedConnection,
24673
+ bus,
24674
+ targetLayer,
24675
+ track: track.value,
24676
+ exitAxis,
24156
24677
  layerNames,
24157
- via,
24158
- additionalViaIndex + 1
24159
- );
24160
- if (viaObstacle) viaObstacles.push(viaObstacle);
24161
- }
24162
- if (plan.planeEndpointVia) {
24163
- const endpointViaObstacle = createViaObstacle(
24678
+ traceWidth,
24679
+ viaDiameter,
24680
+ viaHoleDiameter,
24681
+ viaHandedness,
24682
+ interstitialEscape,
24683
+ spreadLaneIndex: Math.min(
24684
+ connectionRank,
24685
+ bus.connections.length - connectionRank - 1
24686
+ ),
24687
+ cornerExitLaneOffset: cornerLaneOffsets.exit,
24688
+ cornerLocalChannelLaneOffset: cornerLaneOffsets.localChannel,
24689
+ cornerBoundaryChannelLaneOffset: cornerLaneOffsets.boundaryChannel,
24690
+ clearance,
24691
+ terminateAtVia: false,
24692
+ allowBlindAndBuriedVias,
24693
+ cornerBandTargetTrackOffset
24694
+ });
24695
+ if (!planIsClear({
24164
24696
  plan,
24165
- layerNames,
24166
- plan.planeEndpointVia,
24167
- "endpoint"
24697
+ otherPlans: [...acceptedPlans, ...candidatePlans],
24698
+ staticClearanceCache,
24699
+ blockingBusCounts,
24700
+ cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}:${bus.exitEdge ?? "legacy"}:${cornerLaneOffsets.exit}:${cornerLaneOffsets.localChannel}:${cornerLaneOffsets.boundaryChannel}:${cornerBandTargetTrackOffset ?? 0}`,
24701
+ srj,
24702
+ sharedBoundary: bus.sharedBoundary,
24703
+ clearance,
24704
+ allowBlindAndBuriedVias,
24705
+ allowSameNetMerges
24706
+ })) {
24707
+ continue;
24708
+ }
24709
+ searchConnectionOrder(
24710
+ connectionOrder,
24711
+ viaHandedness,
24712
+ connectionIndex + 1,
24713
+ [...candidatePlans, plan]
24168
24714
  );
24169
- if (endpointViaObstacle) viaObstacles.push(endpointViaObstacle);
24715
+ if (alternatives.length >= maxAlternatives) return;
24716
+ if (maxAlternatives === 1) return;
24170
24717
  }
24171
- }
24172
- const planTraces = plans.flatMap((plan) => [
24173
- plan.trace,
24174
- ...plan.planeEndpointTrace ? [plan.planeEndpointTrace] : []
24175
- ]);
24176
- const coordinateRoutePoints = planTraces.flatMap(
24177
- (trace) => trace.route.filter(
24178
- (routePoint) => "x" in routePoint && "y" in routePoint
24179
- )
24180
- );
24181
- const boundsMargin = Math.max(
24182
- inputSrj.defaultObstacleMargin ?? 0,
24183
- inputSrj.minTraceWidth
24184
- );
24185
- const outputBounds = coordinateRoutePoints.length === 0 ? { ...inputSrj.bounds } : {
24186
- minX: Math.min(
24187
- inputSrj.bounds.minX,
24188
- ...coordinateRoutePoints.map(
24189
- (routePoint) => routePoint.x - boundsMargin
24190
- )
24191
- ),
24192
- maxX: Math.max(
24193
- inputSrj.bounds.maxX,
24194
- ...coordinateRoutePoints.map(
24195
- (routePoint) => routePoint.x + boundsMargin
24196
- )
24197
- ),
24198
- minY: Math.min(
24199
- inputSrj.bounds.minY,
24200
- ...coordinateRoutePoints.map(
24201
- (routePoint) => routePoint.y - boundsMargin
24202
- )
24203
- ),
24204
- maxY: Math.max(
24205
- inputSrj.bounds.maxY,
24206
- ...coordinateRoutePoints.map(
24207
- (routePoint) => routePoint.y + boundsMargin
24208
- )
24209
- )
24210
- };
24211
- return {
24212
- ...inputSrj,
24213
- fanoutPlaneConnectivity: plans.flatMap(
24214
- (plan) => plan.termination.type === "plane" ? [
24215
- {
24216
- connectionName: plan.connectionName,
24217
- layer: plan.termination.layer
24218
- }
24219
- ] : []
24220
- ),
24221
- bounds: outputBounds,
24222
- connections: outputConnections.filter(
24223
- (connection) => !planeTerminatedConnectionNames.has(connection.name)
24224
- ),
24225
- buses: inputSrj.buses?.map((bus) => ({
24226
- ...bus,
24227
- connectionNames: bus.connectionNames.filter(
24228
- (connectionName) => !planeTerminatedConnectionNames.has(connectionName)
24229
- )
24230
- })).filter((bus) => bus.connectionNames.length > 0),
24231
- obstacles: [
24232
- ...inputSrj.obstacles.map((obstacle) => ({
24233
- ...obstacle,
24234
- center: { ...obstacle.center },
24235
- layers: [...obstacle.layers],
24236
- connectedTo: [...obstacle.connectedTo]
24237
- })),
24238
- ...viaObstacles
24239
- ],
24240
- traces: [
24241
- ...(inputSrj.traces ?? []).map((trace) => ({
24242
- ...trace,
24243
- route: trace.route.map((routePoint) => ({ ...routePoint }))
24244
- })),
24245
- ...planTraces
24246
- ]
24247
24718
  };
24719
+ for (const viaHandedness of viaHandednesses) {
24720
+ for (const connectionOrder of getConnectionOrders(bus)) {
24721
+ searchConnectionOrder(connectionOrder, viaHandedness, 0, []);
24722
+ if (alternatives.length >= maxAlternatives) return alternatives;
24723
+ }
24724
+ }
24725
+ return alternatives;
24726
+ }
24727
+ function routeBusAlternatives(params, maxAlternatives = 1) {
24728
+ const steps = routeBusAlternativesSteps(params, maxAlternatives);
24729
+ let result = steps.next();
24730
+ while (!result.done) result = steps.next();
24731
+ return result.value;
24732
+ }
24733
+ function routeBus(params) {
24734
+ return routeBusAlternatives(params, 1)[0] ?? null;
24248
24735
  }
24249
24736
 
24250
24737
  // node_modules/@tscircuit/fanout-solver/lib/match-bus-lengths.ts
24251
- var EPSILON7 = 1e-6;
24738
+ var EPSILON8 = 1e-6;
24252
24739
  function pointsMatch2(first, second) {
24253
- return distance9(first, second) <= EPSILON7;
24740
+ return distance9(first, second) <= EPSILON8;
24254
24741
  }
24255
24742
  function getPlanVias3(plan) {
24256
24743
  return [plan.via, ...plan.additionalVias ?? []].filter(
@@ -24355,26 +24842,26 @@ function splitSegmentAtDenseBounds(params) {
24355
24842
  const deltaY = segment2.end.y - segment2.start.y;
24356
24843
  const splitParameters = [0, 1];
24357
24844
  const addSplitParameter = (parameter) => {
24358
- if (parameter <= EPSILON7 || parameter >= 1 - EPSILON7) return;
24845
+ if (parameter <= EPSILON8 || parameter >= 1 - EPSILON8) return;
24359
24846
  const point6 = {
24360
24847
  x: segment2.start.x + deltaX * parameter,
24361
24848
  y: segment2.start.y + deltaY * parameter
24362
24849
  };
24363
- if (point6.x < expandedBounds.minX - EPSILON7 || point6.x > expandedBounds.maxX + EPSILON7 || point6.y < expandedBounds.minY - EPSILON7 || point6.y > expandedBounds.maxY + EPSILON7) {
24850
+ if (point6.x < expandedBounds.minX - EPSILON8 || point6.x > expandedBounds.maxX + EPSILON8 || point6.y < expandedBounds.minY - EPSILON8 || point6.y > expandedBounds.maxY + EPSILON8) {
24364
24851
  return;
24365
24852
  }
24366
24853
  splitParameters.push(parameter);
24367
24854
  };
24368
- if (Math.abs(deltaX) > EPSILON7) {
24855
+ if (Math.abs(deltaX) > EPSILON8) {
24369
24856
  addSplitParameter((expandedBounds.minX - segment2.start.x) / deltaX);
24370
24857
  addSplitParameter((expandedBounds.maxX - segment2.start.x) / deltaX);
24371
24858
  }
24372
- if (Math.abs(deltaY) > EPSILON7) {
24859
+ if (Math.abs(deltaY) > EPSILON8) {
24373
24860
  addSplitParameter((expandedBounds.minY - segment2.start.y) / deltaY);
24374
24861
  addSplitParameter((expandedBounds.maxY - segment2.start.y) / deltaY);
24375
24862
  }
24376
24863
  const parameters = splitParameters.toSorted((first, second) => first - second).filter(
24377
- (parameter, index, values) => index === 0 || Math.abs(parameter - values[index - 1]) > EPSILON7
24864
+ (parameter, index, values) => index === 0 || Math.abs(parameter - values[index - 1]) > EPSILON8
24378
24865
  );
24379
24866
  return parameters.slice(1).map((endParameter, index) => {
24380
24867
  const startParameter = parameters[index];
@@ -24421,7 +24908,7 @@ function hasNonAdjacentSelfIntersection(segments) {
24421
24908
  first.end,
24422
24909
  second.start,
24423
24910
  second.end
24424
- ) <= EPSILON7) {
24911
+ ) <= EPSILON8) {
24425
24912
  return true;
24426
24913
  }
24427
24914
  }
@@ -24463,7 +24950,7 @@ function replacementCopperIsSelfClear(params) {
24463
24950
  const replacement = segments[replacementIndex];
24464
24951
  for (const [otherIndex, other] of segments.entries()) {
24465
24952
  const requiredCenterlineClearance = replacement.width / 2 + other.width / 2 + clearance;
24466
- if (getConnectedPathDistance(replacementIndex, otherIndex) <= requiredCenterlineClearance + EPSILON7) {
24953
+ if (getConnectedPathDistance(replacementIndex, otherIndex) <= requiredCenterlineClearance + EPSILON8) {
24467
24954
  continue;
24468
24955
  }
24469
24956
  if (!segmentsAreClear(replacement, other, clearance)) return false;
@@ -24473,7 +24960,7 @@ function replacementCopperIsSelfClear(params) {
24473
24960
  if (pointsMatch2(via.center, replacement.start) || pointsMatch2(via.center, replacement.end)) {
24474
24961
  continue;
24475
24962
  }
24476
- if (distancePointToSegment(via.center, replacement.start, replacement.end) < via.diameter / 2 + replacement.width / 2 + clearance - EPSILON7) {
24963
+ if (distancePointToSegment(via.center, replacement.start, replacement.end) < via.diameter / 2 + replacement.width / 2 + clearance - EPSILON8) {
24477
24964
  return false;
24478
24965
  }
24479
24966
  }
@@ -24481,7 +24968,7 @@ function replacementCopperIsSelfClear(params) {
24481
24968
  return true;
24482
24969
  }
24483
24970
  function pointIsInsideBounds2(point6, bounds) {
24484
- return point6.x >= bounds.minX - EPSILON7 && point6.x <= bounds.maxX + EPSILON7 && point6.y >= bounds.minY - EPSILON7 && point6.y <= bounds.maxY + EPSILON7;
24971
+ return point6.x >= bounds.minX - EPSILON8 && point6.x <= bounds.maxX + EPSILON8 && point6.y >= bounds.minY - EPSILON8 && point6.y <= bounds.maxY + EPSILON8;
24485
24972
  }
24486
24973
  function addScaled(point6, vector2, scale3) {
24487
24974
  return {
@@ -24501,9 +24988,9 @@ function createMeanderPoints(params) {
24501
24988
  const dx = segment2.end.x - segment2.start.x;
24502
24989
  const dy = segment2.end.y - segment2.start.y;
24503
24990
  const segmentLength = Math.hypot(dx, dy);
24504
- if (segmentLength <= EPSILON7) return null;
24505
- const isAxisAligned = Math.abs(dx) <= EPSILON7 || Math.abs(dy) <= EPSILON7;
24506
- const isFortyFiveDegree = Math.abs(Math.abs(dx) - Math.abs(dy)) <= EPSILON7;
24991
+ if (segmentLength <= EPSILON8) return null;
24992
+ const isAxisAligned = Math.abs(dx) <= EPSILON8 || Math.abs(dy) <= EPSILON8;
24993
+ const isFortyFiveDegree = Math.abs(Math.abs(dx) - Math.abs(dy)) <= EPSILON8;
24507
24994
  if (!isAxisAligned && !isFortyFiveDegree) return null;
24508
24995
  const tangent = { x: dx / segmentLength, y: dy / segmentLength };
24509
24996
  const normal = {
@@ -24519,11 +25006,11 @@ function createMeanderPoints(params) {
24519
25006
  const toothGap = pitch;
24520
25007
  const occupiedLength = toothCount * toothSpan + Math.max(0, toothCount - 1) * toothGap;
24521
25008
  const minimumLead = pitch / 4;
24522
- if (occupiedLength + minimumLead * 2 > segmentLength + EPSILON7) {
25009
+ if (occupiedLength + minimumLead * 2 > segmentLength + EPSILON8) {
24523
25010
  return null;
24524
25011
  }
24525
25012
  const minimumAddedLengthPerTooth = 4 * chamfer * (Math.SQRT2 - 1);
24526
- if (targetAddedLength + EPSILON7 < minimumAddedLengthPerTooth * toothCount) {
25013
+ if (targetAddedLength + EPSILON8 < minimumAddedLengthPerTooth * toothCount) {
24527
25014
  return null;
24528
25015
  }
24529
25016
  const verticalRun = (targetAddedLength / toothCount - minimumAddedLengthPerTooth) / 2;
@@ -24656,10 +25143,10 @@ function* createSpreadLaneCandidates(plan, clearance) {
24656
25143
  (a, b) => distance9(b.segment.start, b.segment.end) - distance9(a.segment.start, a.segment.end)
24657
25144
  )) {
24658
25145
  const length7 = distance9(segment2.start, segment2.end);
24659
- if (length7 <= EPSILON7) continue;
25146
+ if (length7 <= EPSILON8) continue;
24660
25147
  const dx = Math.abs(segment2.end.x - segment2.start.x);
24661
25148
  const dy = Math.abs(segment2.end.y - segment2.start.y);
24662
- if (dx > EPSILON7 && dy > EPSILON7 && Math.abs(dx - dy) > EPSILON7) continue;
25149
+ if (dx > EPSILON8 && dy > EPSILON8 && Math.abs(dx - dy) > EPSILON8) continue;
24663
25150
  const tangent = {
24664
25151
  x: (segment2.end.x - segment2.start.x) / length7,
24665
25152
  y: (segment2.end.y - segment2.start.y) / length7
@@ -24729,15 +25216,15 @@ function matchBusPlanLengths(params) {
24729
25216
  }
24730
25217
  const maxLengthSkew = bus.maxLengthSkew;
24731
25218
  const skew = getBusSkew(busPlans);
24732
- if (skew <= maxLengthSkew + EPSILON7) break;
25219
+ if (skew <= maxLengthSkew + EPSILON8) break;
24733
25220
  const shortest = busPlans.toSorted(
24734
25221
  (first, second) => first.length - second.length || first.connectionName.localeCompare(second.connectionName)
24735
25222
  )[0];
24736
25223
  const longestLength = Math.max(...busPlans.map((plan) => plan.length));
24737
25224
  const deficit = longestLength - shortest.length;
24738
25225
  const minimumRequiredAddition = Math.max(
24739
- EPSILON7,
24740
- deficit - maxLengthSkew + EPSILON7
25226
+ EPSILON8,
25227
+ deficit - maxLengthSkew + EPSILON8
24741
25228
  );
24742
25229
  const targetAddedLengths = [
24743
25230
  minimumRequiredAddition,
@@ -24747,8 +25234,8 @@ function matchBusPlanLengths(params) {
24747
25234
  deficit,
24748
25235
  deficit + maxLengthSkew
24749
25236
  ].filter(
24750
- (value, index, values) => value > EPSILON7 && values.findIndex(
24751
- (candidate) => Math.abs(candidate - value) < EPSILON7
25237
+ (value, index, values) => value > EPSILON8 && values.findIndex(
25238
+ (candidate) => Math.abs(candidate - value) < EPSILON8
24752
25239
  ) === index
24753
25240
  ).toSorted((first, second) => first - second);
24754
25241
  let acceptedPlans = null;
@@ -24759,7 +25246,7 @@ function matchBusPlanLengths(params) {
24759
25246
  const nextBusPlans = nextPlans.filter(
24760
25247
  (plan) => plan.busId === bus.busId
24761
25248
  );
24762
- if (getBusSkew(nextBusPlans) > skew + EPSILON7) return null;
25249
+ if (getBusSkew(nextBusPlans) > skew + EPSILON8) return null;
24763
25250
  if (!fanoutPlansAreClear({
24764
25251
  plans: nextPlans,
24765
25252
  srj: inputSrj,
@@ -24793,7 +25280,7 @@ function matchBusPlanLengths(params) {
24793
25280
  const search = (currentPlan, stagesRemaining) => {
24794
25281
  const addedLength = currentPlan.length - shortest.length;
24795
25282
  const remainingAddition = targetAddedLength - addedLength;
24796
- if (remainingAddition <= EPSILON7) {
25283
+ if (remainingAddition <= EPSILON8) {
24797
25284
  return acceptCandidate(currentPlan);
24798
25285
  }
24799
25286
  if (stagesRemaining <= 0) return null;
@@ -24838,7 +25325,7 @@ function matchBusPlanLengths(params) {
24838
25325
  if (!acceptedPlans) continue;
24839
25326
  break;
24840
25327
  }
24841
- if (!acceptedPlans && Math.abs(targetAddedLength - minimumRequiredAddition) <= EPSILON7) {
25328
+ if (!acceptedPlans && Math.abs(targetAddedLength - minimumRequiredAddition) <= EPSILON8) {
24842
25329
  acceptedPlans = findMultiSpanCandidate(targetAddedLength);
24843
25330
  }
24844
25331
  if (acceptedPlans) break;
@@ -24880,7 +25367,7 @@ function matchBusPlanLengths(params) {
24880
25367
  const matchedBusPlans = matchedPlans.filter(
24881
25368
  (plan) => plan.busId === bus.busId
24882
25369
  );
24883
- if (getBusSkew(matchedBusPlans) > bus.maxLengthSkew + EPSILON7) {
25370
+ if (getBusSkew(matchedBusPlans) > bus.maxLengthSkew + EPSILON8) {
24884
25371
  return { plans: null, failedBus: bus };
24885
25372
  }
24886
25373
  }
@@ -25862,7 +26349,7 @@ function prepareFanoutBuses(srj, options) {
25862
26349
  }
25863
26350
 
25864
26351
  // node_modules/@tscircuit/fanout-solver/lib/route-single-layer-adaptive-exits.ts
25865
- var EPSILON8 = 1e-9;
26352
+ var EPSILON9 = 1e-9;
25866
26353
  var OBSTACLE_BIN_SIZE = 1;
25867
26354
  var FANOUT_FLOW_DEBUG_ENABLED = globalThis.process?.env?.FANOUT_FLOW_DEBUG === "1";
25868
26355
  function visualizeFlowProgress(params) {
@@ -25943,7 +26430,7 @@ function connectWith45DegreeSegments(start, end) {
25943
26430
  const deltaY = end.y - start.y;
25944
26431
  const absoluteX = Math.abs(deltaX);
25945
26432
  const absoluteY = Math.abs(deltaY);
25946
- if (absoluteX < EPSILON8 || absoluteY < EPSILON8 || Math.abs(absoluteX - absoluteY) < EPSILON8) {
26433
+ if (absoluteX < EPSILON9 || absoluteY < EPSILON9 || Math.abs(absoluteX - absoluteY) < EPSILON9) {
25947
26434
  return [start, end];
25948
26435
  }
25949
26436
  if (absoluteX > absoluteY) {
@@ -25972,7 +26459,7 @@ function enforceStraightOr45DegreeSegments(points) {
25972
26459
  const start = normalized.at(-1);
25973
26460
  const deltaX = Math.abs(end.x - start.x);
25974
26461
  const deltaY = Math.abs(end.y - start.y);
25975
- if (deltaX < EPSILON8 || deltaY < EPSILON8 || Math.abs(deltaX - deltaY) < EPSILON8) {
26462
+ if (deltaX < EPSILON9 || deltaY < EPSILON9 || Math.abs(deltaX - deltaY) < EPSILON9) {
25976
26463
  normalized.push(end);
25977
26464
  continue;
25978
26465
  }
@@ -26005,7 +26492,7 @@ function chamferOrthogonalPolyline3(points, requestedChamfer) {
26005
26492
  const next = points[index + 1];
26006
26493
  const incomingLength = distance9(previous, corner);
26007
26494
  const outgoingLength = distance9(corner, next);
26008
- if (incomingLength < EPSILON8 || outgoingLength < EPSILON8) continue;
26495
+ if (incomingLength < EPSILON9 || outgoingLength < EPSILON9) continue;
26009
26496
  const incoming = {
26010
26497
  x: (corner.x - previous.x) / incomingLength,
26011
26498
  y: (corner.y - previous.y) / incomingLength
@@ -26038,7 +26525,7 @@ function chamferOrthogonalPolyline3(points, requestedChamfer) {
26038
26525
  function getSegments2(points, traceWidth) {
26039
26526
  const segments = [];
26040
26527
  for (let index = 1; index < points.length; index++) {
26041
- if (distance9(points[index - 1], points[index]) < EPSILON8) continue;
26528
+ if (distance9(points[index - 1], points[index]) < EPSILON9) continue;
26042
26529
  segments.push({
26043
26530
  start: points[index - 1],
26044
26531
  end: points[index],
@@ -26189,7 +26676,7 @@ function* createFlowGridSteps(params) {
26189
26676
  for (let node = 0; node < nodeCount; node++) {
26190
26677
  const point6 = points[node];
26191
26678
  if (getNearbyObstacles(point6).every(
26192
- (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredObstacleDistance - EPSILON8
26679
+ (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredObstacleDistance - EPSILON9
26193
26680
  )) {
26194
26681
  obstacleFreeNodes[node] = 1;
26195
26682
  }
@@ -26228,7 +26715,7 @@ function* createFlowGridSteps(params) {
26228
26715
  layer: "top"
26229
26716
  };
26230
26717
  if (getNearbyObstacles(segment2.start, segment2.end).some(
26231
- (obstacle) => distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleDistance - EPSILON8
26718
+ (obstacle) => distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleDistance - EPSILON9
26232
26719
  )) {
26233
26720
  continue;
26234
26721
  }
@@ -26293,7 +26780,7 @@ function* routeDirectionGroupSteps(params) {
26293
26780
  const segments = getSegments2(points, traceWidth);
26294
26781
  return segments.every(
26295
26782
  (segment2) => obstacles.every(
26296
- (obstacle) => obstacleBelongsToItem(obstacle, item) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON8
26783
+ (obstacle) => obstacleBelongsToItem(obstacle, item) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON9
26297
26784
  )
26298
26785
  ) && segments.every(
26299
26786
  (segment2) => acceptedSegments.every(
@@ -26302,7 +26789,7 @@ function* routeDirectionGroupSteps(params) {
26302
26789
  segment2.end,
26303
26790
  acceptedSegment.start,
26304
26791
  acceptedSegment.end
26305
- ) >= requiredRouteDistance - EPSILON8
26792
+ ) >= requiredRouteDistance - EPSILON9
26306
26793
  )
26307
26794
  );
26308
26795
  };
@@ -26392,7 +26879,7 @@ function* routeDirectionGroupSteps(params) {
26392
26879
  segment2.end,
26393
26880
  otherSegment.start,
26394
26881
  otherSegment.end
26395
- ) >= requiredRouteDistance - EPSILON8
26882
+ ) >= requiredRouteDistance - EPSILON9
26396
26883
  )
26397
26884
  )
26398
26885
  );
@@ -26460,7 +26947,7 @@ function* routeDirectionGroupSteps(params) {
26460
26947
  const node = row * columnCount + column;
26461
26948
  if (!freeNodes[node] || terminalNodes.has(node)) continue;
26462
26949
  const point6 = pointForNode(node);
26463
- if (distanceSegmentToSegment(segment2.start, segment2.end, point6, point6) < requiredRouteDistance - EPSILON8) {
26950
+ if (distanceSegmentToSegment(segment2.start, segment2.end, point6, point6) < requiredRouteDistance - EPSILON9) {
26464
26951
  freeNodes[node] = 0;
26465
26952
  }
26466
26953
  }
@@ -26680,7 +27167,7 @@ function buildPlan3(route, traceWidth) {
26680
27167
  function getConnectorVariants2(start, end) {
26681
27168
  const first = connectWith45DegreeSegments(start, end);
26682
27169
  const second = connectWith45DegreeSegments(end, start).reverse();
26683
- return first.length === second.length && first.every((point6, index) => distance9(point6, second[index]) < EPSILON8) ? [first] : [first, second];
27170
+ return first.length === second.length && first.every((point6, index) => distance9(point6, second[index]) < EPSILON9) ? [first] : [first, second];
26684
27171
  }
26685
27172
  function plansHaveRequiredClearance(params) {
26686
27173
  const { plans, items, obstacles, traceWidth, clearance } = params;
@@ -26707,7 +27194,7 @@ function plansHaveRequiredClearance(params) {
26707
27194
  for (const [netKey, segments] of uniqueSegmentsByNet) {
26708
27195
  for (const segment2 of segments) {
26709
27196
  for (const obstacle of obstacles) {
26710
- if (obstacle.connectedTo.includes(netKey) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON8) {
27197
+ if (obstacle.connectedTo.includes(netKey) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON9) {
26711
27198
  continue;
26712
27199
  }
26713
27200
  if (FANOUT_FLOW_DEBUG_ENABLED) {
@@ -26734,7 +27221,7 @@ function plansHaveRequiredClearance(params) {
26734
27221
  first.end,
26735
27222
  second.start,
26736
27223
  second.end
26737
- ) < requiredRouteDistance - EPSILON8) {
27224
+ ) < requiredRouteDistance - EPSILON9) {
26738
27225
  if (FANOUT_FLOW_DEBUG_ENABLED) {
26739
27226
  console.error("single-layer routes violate copper clearance", {
26740
27227
  firstNetKey: entries[firstIndex][0],
@@ -26759,10 +27246,10 @@ function plansHaveRequiredClearance(params) {
26759
27246
  return true;
26760
27247
  }
26761
27248
  function getDirectionForBoundaryPoint(point6, boundary) {
26762
- if (Math.abs(point6.x - boundary.minX) < EPSILON8) return "left";
26763
- if (Math.abs(point6.x - boundary.maxX) < EPSILON8) return "right";
26764
- if (Math.abs(point6.y - boundary.minY) < EPSILON8) return "down";
26765
- if (Math.abs(point6.y - boundary.maxY) < EPSILON8) return "up";
27249
+ if (Math.abs(point6.x - boundary.minX) < EPSILON9) return "left";
27250
+ if (Math.abs(point6.x - boundary.maxX) < EPSILON9) return "right";
27251
+ if (Math.abs(point6.y - boundary.minY) < EPSILON9) return "down";
27252
+ if (Math.abs(point6.y - boundary.maxY) < EPSILON9) return "up";
26766
27253
  return null;
26767
27254
  }
26768
27255
  function* routeWithAdaptiveExitsSteps(params) {
@@ -26852,7 +27339,7 @@ function* routeWithAdaptiveExitsSteps(params) {
26852
27339
  const connectorSegments = getSegments2(connectorPoints, traceWidth);
26853
27340
  const clearsObstacles = connectorSegments.every(
26854
27341
  (segment2) => obstacles.every(
26855
- (obstacle) => obstacleBelongsToItem(obstacle, mergeItem) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON8
27342
+ (obstacle) => obstacleBelongsToItem(obstacle, mergeItem) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON9
26856
27343
  )
26857
27344
  );
26858
27345
  const clearsRoutes = connectorSegments.every(
@@ -26863,7 +27350,7 @@ function* routeWithAdaptiveExitsSteps(params) {
26863
27350
  segment2.end,
26864
27351
  otherSegment.start,
26865
27352
  otherSegment.end
26866
- ) >= requiredRouteDistance - EPSILON8
27353
+ ) >= requiredRouteDistance - EPSILON9
26867
27354
  )
26868
27355
  )
26869
27356
  );
@@ -27938,10 +28425,62 @@ function routeSingleLayerWithPushAndShove(params) {
27938
28425
  return paths.map(buildPlan4);
27939
28426
  }
27940
28427
 
28428
+ // node_modules/@tscircuit/fanout-solver/lib/runtime-process.ts
28429
+ var getRuntimeProcess = (runtime) => runtime.process ?? { env: {} };
28430
+
28431
+ // node_modules/@tscircuit/fanout-solver/lib/shorten-bus-plans.ts
28432
+ function shortenBusPlans(params) {
28433
+ const { bus } = params;
28434
+ let plans = [...params.plans];
28435
+ if (bus.maxLengthSkew === void 0) return plans;
28436
+ const busPlans = plans.filter((plan) => plan.busId === bus.busId).toSorted((first, second) => second.length - first.length);
28437
+ for (const plan of busPlans) {
28438
+ const minimumLength = Math.min(
28439
+ ...plans.filter((p) => p.busId === bus.busId).map((p) => p.length)
28440
+ );
28441
+ if (plan.length - minimumLength <= bus.maxLengthSkew) continue;
28442
+ if (!plan.via || plan.additionalVias?.length || plan.planeEndpointVia || plan.segments.filter((segment2) => segment2.layer === plan.sourceLayer).length !== 1)
28443
+ continue;
28444
+ const connection = bus.connections.find(
28445
+ (c) => c.connectionIndex === plan.connectionIndex
28446
+ );
28447
+ for (const alignGridToPads of [true, false]) {
28448
+ const candidate = routeViaMinimalWinding({
28449
+ ...params,
28450
+ bus: {
28451
+ ...bus,
28452
+ connections: [connection],
28453
+ routableEscapeLayers: [plan.targetLayer]
28454
+ },
28455
+ terminals: [
28456
+ { connection, viaPoint: plan.via.center, exitPoint: plan.exitPoint }
28457
+ ],
28458
+ targetLayer: plan.targetLayer,
28459
+ acceptedPlans: plans.filter((p) => p !== plan),
28460
+ allowBlindAndBuriedVias: false,
28461
+ gridStepDivisor: 2,
28462
+ alignGridToPads,
28463
+ maximumRouteOrderAttempts: 1
28464
+ })?.[0];
28465
+ if (!candidate || candidate.length >= plan.length - 1e-6) continue;
28466
+ const nextPlans = plans.map((p) => p === plan ? candidate : p);
28467
+ if (!fanoutPlansAreClear({
28468
+ ...params,
28469
+ plans: nextPlans,
28470
+ allowBlindAndBuriedVias: false
28471
+ }))
28472
+ continue;
28473
+ plans = nextPlans;
28474
+ break;
28475
+ }
28476
+ }
28477
+ return plans;
28478
+ }
28479
+
27941
28480
  // node_modules/@tscircuit/fanout-solver/lib/validate-fanout-solution.ts
27942
- var EPSILON9 = 1e-6;
28481
+ var EPSILON10 = 1e-6;
27943
28482
  function pointsMatch3(first, second) {
27944
- return distance9(first, second) <= EPSILON9;
28483
+ return distance9(first, second) <= EPSILON10;
27945
28484
  }
27946
28485
  function getPointLayers3(point6) {
27947
28486
  return "layer" in point6 ? [point6.layer] : point6.layers;
@@ -27960,26 +28499,26 @@ function connectionPointsMatch(first, second) {
27960
28499
  return pointsMatch3(first, second) && getPointLayers3(first).join("\0") === getPointLayers3(second).join("\0") && first.pointId === second.pointId && first.pcb_port_id === second.pcb_port_id;
27961
28500
  }
27962
28501
  function pointIsOnBoundary(point6, boundary) {
27963
- const inside = point6.x >= boundary.minX - EPSILON9 && point6.x <= boundary.maxX + EPSILON9 && point6.y >= boundary.minY - EPSILON9 && point6.y <= boundary.maxY + EPSILON9;
27964
- const onEdge = Math.abs(point6.x - boundary.minX) <= EPSILON9 || Math.abs(point6.x - boundary.maxX) <= EPSILON9 || Math.abs(point6.y - boundary.minY) <= EPSILON9 || Math.abs(point6.y - boundary.maxY) <= EPSILON9;
28502
+ const inside = point6.x >= boundary.minX - EPSILON10 && point6.x <= boundary.maxX + EPSILON10 && point6.y >= boundary.minY - EPSILON10 && point6.y <= boundary.maxY + EPSILON10;
28503
+ const onEdge = Math.abs(point6.x - boundary.minX) <= EPSILON10 || Math.abs(point6.x - boundary.maxX) <= EPSILON10 || Math.abs(point6.y - boundary.minY) <= EPSILON10 || Math.abs(point6.y - boundary.maxY) <= EPSILON10;
27965
28504
  return inside && onEdge;
27966
28505
  }
27967
28506
  function pointIsOnBoundaryEdge(point6, edge, boundary) {
27968
- const inside = point6.x >= boundary.minX - EPSILON9 && point6.x <= boundary.maxX + EPSILON9 && point6.y >= boundary.minY - EPSILON9 && point6.y <= boundary.maxY + EPSILON9;
28507
+ const inside = point6.x >= boundary.minX - EPSILON10 && point6.x <= boundary.maxX + EPSILON10 && point6.y >= boundary.minY - EPSILON10 && point6.y <= boundary.maxY + EPSILON10;
27969
28508
  if (!inside) return false;
27970
28509
  switch (edge) {
27971
28510
  case "left":
27972
- return Math.abs(point6.x - boundary.minX) <= EPSILON9;
28511
+ return Math.abs(point6.x - boundary.minX) <= EPSILON10;
27973
28512
  case "right":
27974
- return Math.abs(point6.x - boundary.maxX) <= EPSILON9;
28513
+ return Math.abs(point6.x - boundary.maxX) <= EPSILON10;
27975
28514
  case "top":
27976
- return Math.abs(point6.y - boundary.maxY) <= EPSILON9;
28515
+ return Math.abs(point6.y - boundary.maxY) <= EPSILON10;
27977
28516
  case "bottom":
27978
- return Math.abs(point6.y - boundary.minY) <= EPSILON9;
28517
+ return Math.abs(point6.y - boundary.minY) <= EPSILON10;
27979
28518
  }
27980
28519
  }
27981
28520
  function pointIsInsideBounds3(point6, bounds) {
27982
- return point6.x >= bounds.minX - EPSILON9 && point6.x <= bounds.maxX + EPSILON9 && point6.y >= bounds.minY - EPSILON9 && point6.y <= bounds.maxY + EPSILON9;
28521
+ return point6.x >= bounds.minX - EPSILON10 && point6.x <= bounds.maxX + EPSILON10 && point6.y >= bounds.minY - EPSILON10 && point6.y <= bounds.maxY + EPSILON10;
27983
28522
  }
27984
28523
  function addIssue2(issues, code, message, plan, otherConnectionName) {
27985
28524
  issues.push({
@@ -28112,7 +28651,7 @@ function validatePlanStructure(params) {
28112
28651
  );
28113
28652
  }
28114
28653
  const isAllowedViaInPadPlaneTermination = inputSrj.allowViaInPad === true && plan.termination.type === "plane" && plan.via !== void 0 && pointsMatch3(plan.via.center, plan.sourcePoint) && pointsMatch3(plan.exitPoint, plan.sourcePoint);
28115
- if (!isAllowedViaInPadPlaneTermination && (plan.segments.length === 0 || plan.length <= EPSILON9)) {
28654
+ if (!isAllowedViaInPadPlaneTermination && (plan.segments.length === 0 || plan.length <= EPSILON10)) {
28116
28655
  addIssue2(
28117
28656
  issues,
28118
28657
  "not-broken-out",
@@ -28195,7 +28734,7 @@ function validatePlanStructure(params) {
28195
28734
  });
28196
28735
  if (traceSegments.length !== plan.segments.length || traceSegments.some((segment2, index) => {
28197
28736
  const declared = plan.segments[index];
28198
- return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON9 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28737
+ return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON10 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28199
28738
  })) {
28200
28739
  addIssue2(
28201
28740
  issues,
@@ -28220,8 +28759,8 @@ function validatePlanStructure(params) {
28220
28759
  });
28221
28760
  if (endpointSegments.length !== plan.planeEndpointSegments.length || endpointSegments.some((segment2, index) => {
28222
28761
  const declared = plan.planeEndpointSegments?.[index];
28223
- return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON9 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28224
- }) || distance9(plan.planeEndpointVia.center, plan.targetPoint) <= EPSILON9) {
28762
+ return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON10 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28763
+ }) || distance9(plan.planeEndpointVia.center, plan.targetPoint) <= EPSILON10) {
28225
28764
  addIssue2(
28226
28765
  issues,
28227
28766
  "trace-plan-mismatch",
@@ -28301,7 +28840,7 @@ function plansHaveConnectedCopper(first, second) {
28301
28840
  firstSegment.end,
28302
28841
  secondSegment.start,
28303
28842
  secondSegment.end
28304
- ) <= (firstSegment.width + secondSegment.width) / 2 + EPSILON9) {
28843
+ ) <= (firstSegment.width + secondSegment.width) / 2 + EPSILON10) {
28305
28844
  return true;
28306
28845
  }
28307
28846
  }
@@ -28310,7 +28849,7 @@ function plansHaveConnectedCopper(first, second) {
28310
28849
  secondVia.center,
28311
28850
  firstSegment.start,
28312
28851
  firstSegment.end
28313
- ) <= secondVia.diameter / 2 + firstSegment.width / 2 + EPSILON9) {
28852
+ ) <= secondVia.diameter / 2 + firstSegment.width / 2 + EPSILON10) {
28314
28853
  return true;
28315
28854
  }
28316
28855
  }
@@ -28321,14 +28860,14 @@ function plansHaveConnectedCopper(first, second) {
28321
28860
  firstVia.center,
28322
28861
  secondSegment.start,
28323
28862
  secondSegment.end
28324
- ) <= firstVia.diameter / 2 + secondSegment.width / 2 + EPSILON9) {
28863
+ ) <= firstVia.diameter / 2 + secondSegment.width / 2 + EPSILON10) {
28325
28864
  return true;
28326
28865
  }
28327
28866
  }
28328
28867
  for (const secondVia of secondVias) {
28329
28868
  if (firstVia.spanLayers.some(
28330
28869
  (layer) => secondVia.spanLayers.includes(layer)
28331
- ) && distance9(firstVia.center, secondVia.center) <= (firstVia.diameter + secondVia.diameter) / 2 + EPSILON9) {
28870
+ ) && distance9(firstVia.center, secondVia.center) <= (firstVia.diameter + secondVia.diameter) / 2 + EPSILON10) {
28332
28871
  return true;
28333
28872
  }
28334
28873
  }
@@ -29037,6 +29576,7 @@ function visualizeSimpleRouteJson(srj) {
29037
29576
  }
29038
29577
 
29039
29578
  // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
29579
+ var process = getRuntimeProcess(globalThis);
29040
29580
  var FanoutWorkSolver = class extends BaseSolver {
29041
29581
  constructor(solverName, generator, getVisualization, getStats, getProgress) {
29042
29582
  super();
@@ -29626,7 +30166,10 @@ var FanoutSolver = class extends BaseSolver {
29626
30166
  buses: this.preparedBuses,
29627
30167
  escapeLayers: this.config.escapeLayers,
29628
30168
  escapeLayersByBusId: this.escapeLayersByBusId,
29629
- preferOrderedCoordinatedWindingLayers: this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0
30169
+ preferOrderedCoordinatedWindingLayers: this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0 || shouldUseAdaptiveDensePlaneRouting(
30170
+ this.preparedBuses,
30171
+ this.config.allowBlindAndBuriedVias
30172
+ )
29630
30173
  }),
29631
30174
  generatedAssignments,
29632
30175
  maxAssignments: this.config.maxLayerCombinations
@@ -29915,6 +30458,8 @@ var FanoutSolver = class extends BaseSolver {
29915
30458
  "plane-match:incremental-failed",
29916
30459
  "plane-route:alternate-candidate-counts",
29917
30460
  "plane-route:alternate-search",
30461
+ "plane-route:csp",
30462
+ "plane-reservation-core",
29918
30463
  "plane-route:promote-failed",
29919
30464
  "plane-route:alternate-choice",
29920
30465
  "plane-route:promote-zero-candidates",
@@ -29931,7 +30476,13 @@ var FanoutSolver = class extends BaseSolver {
29931
30476
  const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
29932
30477
  (bus) => bus.termination.type === "boundary"
29933
30478
  );
29934
- const useConfiguredDensePlaneRouting = this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0;
30479
+ const configuredDensePlaneRouting = this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0;
30480
+ const useAdaptiveDensePlaneRouting = !configuredDensePlaneRouting && shouldUseAdaptiveDensePlaneRouting(
30481
+ this.preparedBuses,
30482
+ this.config.allowBlindAndBuriedVias
30483
+ );
30484
+ const useConfiguredDensePlaneRouting = configuredDensePlaneRouting || useAdaptiveDensePlaneRouting;
30485
+ const useAdaptiveJointPlaneSelection = useAdaptiveDensePlaneRouting && (params.planeReservationRetryCount ?? 0) === 0;
29935
30486
  const matchLengthsAfterPlanes = useConfiguredDensePlaneRouting && params.lengthMatchingStage !== "before-planes";
29936
30487
  const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
29937
30488
  unsortedBoundaryBuses.map((bus) => bus.connections.length)
@@ -30078,6 +30629,17 @@ var FanoutSolver = class extends BaseSolver {
30078
30629
  0,
30079
30630
  Number.isFinite(initialPlaneReservationCount) ? initialPlaneReservationCount : 8
30080
30631
  ) : planeBuses;
30632
+ if (params.promotedPlaneReservationBusIds) {
30633
+ activeBoundaryReservationPlaneBuses = [
30634
+ .../* @__PURE__ */ new Set([
30635
+ ...activeBoundaryReservationPlaneBuses,
30636
+ ...planeBuses.filter(
30637
+ (bus) => params.promotedPlaneReservationBusIds.includes(bus.busId)
30638
+ )
30639
+ ])
30640
+ ];
30641
+ }
30642
+ const unroutablePlaneBusIds = /* @__PURE__ */ new Set();
30081
30643
  debugDense(
30082
30644
  "start",
30083
30645
  boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
@@ -30110,7 +30672,7 @@ var FanoutSolver = class extends BaseSolver {
30110
30672
  const preferBoundaryOutwardByBusId = new Map(
30111
30673
  boundaryBuses.map((bus) => [
30112
30674
  bus.busId,
30113
- bus.connections.length === 1 ? hasThreeWideBoundaryBuses && narrowBusIsEmbeddedInWideSourceField(bus) ? true : useGeometryAwareSingletonOutwardPreference && getCornerBandSide(bus.exitEdge, bus.preferredExit) ? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0 : getExitEdgeForDirection(bus.direction) !== bus.exitEdge : bus.exitEdge === "top" && bus.connections.length >= 8 ? false : getExitEdgeForDirection(bus.direction) !== bus.exitEdge
30675
+ bus.connections.length === 1 ? hasThreeWideBoundaryBuses && narrowBusIsEmbeddedInWideSourceField(bus) ? true : useGeometryAwareSingletonOutwardPreference && getCornerBandSide(bus.exitEdge, bus.preferredExit) ? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0 : getExitEdgeForDirection(bus.direction) !== bus.exitEdge : hasThreeWideBoundaryBuses && bus.connections.length >= 8 ? false : getExitEdgeForDirection(bus.direction) !== bus.exitEdge
30114
30676
  ])
30115
30677
  );
30116
30678
  for (const bus of wideBoundaryBuses) {
@@ -30392,6 +30954,14 @@ var FanoutSolver = class extends BaseSolver {
30392
30954
  return [
30393
30955
  {
30394
30956
  connectionName: connection.connection.name,
30957
+ sourceEscapeSegment: matchedPlans.some(
30958
+ (plan) => plan.connectionIndex === connection.connectionIndex
30959
+ ) ? void 0 : {
30960
+ start: connection.sourcePoint,
30961
+ end: center,
30962
+ layer: connection.sourceLayer,
30963
+ width: this.config.traceWidth
30964
+ },
30395
30965
  via: {
30396
30966
  center,
30397
30967
  diameter: this.config.viaDiameter,
@@ -30446,7 +31016,7 @@ var FanoutSolver = class extends BaseSolver {
30446
31016
  fixedViaPointsByConnectionIndex,
30447
31017
  reservedVias: getReservedVias(bus),
30448
31018
  viaMinimalOnly: process.env.FANOUT_DEBUG_ALLOW_EXTRA_VIAS !== "1",
30449
- allowBoundarySideViaFallback: true,
31019
+ allowBoundarySideViaFallback: bus.connections.length === 1,
30450
31020
  preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
30451
31021
  adaptiveWindingRouteOrder,
30452
31022
  alignWindingGridToPads: usePadAlignedDenseRouting && !useConfiguredDensePlaneRouting,
@@ -30557,7 +31127,21 @@ var FanoutSolver = class extends BaseSolver {
30557
31127
  ))[0];
30558
31128
  if (busPlans) break;
30559
31129
  }
30560
- if (!busPlans) fixedViaPointsByConnectionIndex = originalPoints;
31130
+ if (!busPlans) {
31131
+ fixedViaPointsByConnectionIndex = originalPoints;
31132
+ if (bus.connections.length === 2) {
31133
+ busPlans = (yield* routeAlternatives(
31134
+ {
31135
+ ...routeParams,
31136
+ fixedViaPointsByConnectionIndex: originalPoints,
31137
+ reservedVias: getReservedVias(bus),
31138
+ allowBoundarySideViaFallback: true,
31139
+ fixedViaFallbackRouteOrderAttempts: 1
31140
+ },
31141
+ 1
31142
+ ))[0];
31143
+ }
31144
+ }
30561
31145
  }
30562
31146
  if (busPlans && bus.maxLengthSkew !== void 0) {
30563
31147
  const lengths = busPlans.map((plan) => plan.length);
@@ -31162,6 +31746,73 @@ var FanoutSolver = class extends BaseSolver {
31162
31746
  }))
31163
31747
  })
31164
31748
  );
31749
+ if (useAdaptiveJointPlaneSelection) {
31750
+ const acceptedPlans = [
31751
+ ...candidatePlans,
31752
+ ...feasibleAlternatePlanePlans
31753
+ ];
31754
+ for (const planeBus of planeBuses) {
31755
+ if (matchedPlaneBuses.includes(planeBus) || candidateSets.some((set) => set.planeBus === planeBus))
31756
+ continue;
31757
+ const candidates = [];
31758
+ const sites = getComponentDogboneViaSiteCandidates(
31759
+ [planeBus],
31760
+ {
31761
+ viaDiameter: this.config.viaDiameter,
31762
+ viaHoleDiameter: this.config.viaHoleDiameter,
31763
+ traceWidth: this.config.traceWidth,
31764
+ clearance: this.config.clearance,
31765
+ blockingSegments: acceptedPlans.flatMap(
31766
+ (plan) => plan.segments.map((segment2) => ({
31767
+ connectionIndex: plan.connectionIndex,
31768
+ segment: segment2
31769
+ }))
31770
+ ),
31771
+ blockingVias: acceptedPlans.flatMap(
31772
+ (plan) => [
31773
+ plan.via,
31774
+ ...plan.additionalVias ?? [],
31775
+ plan.planeEndpointVia
31776
+ ].filter((via) => via !== void 0).map((via) => ({
31777
+ connectionIndex: plan.connectionIndex,
31778
+ center: via.center,
31779
+ diameter: via.diameter,
31780
+ spanLayers: via.spanLayers
31781
+ }))
31782
+ ),
31783
+ additionalObstacles: this.routingSrj.obstacles,
31784
+ preferPlaneCheckerboardSites: true,
31785
+ canShareCopper
31786
+ }
31787
+ );
31788
+ for (const site of sites) {
31789
+ const plans = routeBus({
31790
+ srj: this.routingSrj,
31791
+ bus: planeBus,
31792
+ targetLayer: params.busLayerAssignments[planeBus.busId],
31793
+ acceptedPlans,
31794
+ layerNames: this.config.layerNames,
31795
+ traceWidth: this.config.traceWidth,
31796
+ viaDiameter: this.config.viaDiameter,
31797
+ viaHoleDiameter: this.config.viaHoleDiameter,
31798
+ clearance: this.config.clearance,
31799
+ compactBusTracks: this.config.compactBusTracks,
31800
+ allowBlindAndBuriedVias: false,
31801
+ allowSameNetMerges: this.config.allowSameNetMerges,
31802
+ fixedViaPointsByConnectionIndex: /* @__PURE__ */ new Map([
31803
+ [site.connectionIndex, site.point]
31804
+ ])
31805
+ });
31806
+ if (plans)
31807
+ candidates.push({
31808
+ key: `${planeBus.busId}:local:${candidates.length}`,
31809
+ planeBus,
31810
+ plans
31811
+ });
31812
+ }
31813
+ candidateSets.push({ planeBus, candidates });
31814
+ }
31815
+ }
31165
31816
  debugDense(
31166
31817
  "plane-route:alternate-candidate-counts",
31167
31818
  candidateSets.map((candidateSet) => [
@@ -31169,6 +31820,10 @@ var FanoutSolver = class extends BaseSolver {
31169
31820
  candidateSet.candidates.length
31170
31821
  ])
31171
31822
  );
31823
+ for (const candidateSet of candidateSets) {
31824
+ if (candidateSet.candidates.length === 0)
31825
+ unroutablePlaneBusIds.add(candidateSet.planeBus.busId);
31826
+ }
31172
31827
  const compatibilityByCandidatePair = /* @__PURE__ */ new Map();
31173
31828
  const candidatesAreCompatible = (first, second) => {
31174
31829
  const cacheKey = [first.key, second.key].toSorted().join("|");
@@ -31261,10 +31916,70 @@ var FanoutSolver = class extends BaseSolver {
31261
31916
  );
31262
31917
  return null;
31263
31918
  };
31264
- const selectedCandidates = selectCompatiblePlaneRoutes(
31265
- candidateSets,
31266
- []
31267
- );
31919
+ let selectedCandidates;
31920
+ if (useAdaptiveDensePlaneRouting) {
31921
+ const getUniqueDomains = (sets) => sets.map((set) => [
31922
+ ...new Map(
31923
+ set.candidates.map((candidate) => [
31924
+ JSON.stringify(
31925
+ candidate.plans.map((plan) => [
31926
+ plan.segments,
31927
+ plan.via,
31928
+ plan.additionalVias,
31929
+ plan.planeEndpointSegments,
31930
+ plan.planeEndpointVia
31931
+ ])
31932
+ ),
31933
+ candidate
31934
+ ])
31935
+ ).values()
31936
+ ]);
31937
+ const runCandidateSelection = (sets, maximumSearchStates = maximumAlternatePlaneSearchStates) => selectCompatibleCandidates({
31938
+ candidateSets: getUniqueDomains(sets),
31939
+ maximumSearchStates,
31940
+ areCompatible: (first, second) => fanoutPlansAreMutuallyClear({
31941
+ plans: [...first.plans, ...second.plans],
31942
+ srj: this.routingSrj,
31943
+ clearance: this.config.clearance,
31944
+ allowSameNetMerges: this.config.allowSameNetMerges
31945
+ })
31946
+ });
31947
+ const result = runCandidateSelection(candidateSets);
31948
+ debugDense(
31949
+ "plane-route:csp",
31950
+ result.selection ? "complete" : "failed",
31951
+ result.searchStates,
31952
+ result.emptyDomainIndices.map(
31953
+ (index) => candidateSets[index].planeBus.busId
31954
+ )
31955
+ );
31956
+ selectedCandidates = result.selection;
31957
+ alternatePlaneSearchStates = result.searchStates;
31958
+ if (!result.selection && useAdaptiveDensePlaneRouting) {
31959
+ const expanded = result.emptyDomainIndices.map((index) => candidateSets[index].planeBus.busId).filter(
31960
+ (id) => !orderedAlternatePlaneBuses.some(
31961
+ (bus) => bus.busId === id
31962
+ )
31963
+ );
31964
+ if (expanded.length > 0) {
31965
+ debugDense("plane-route:expand-domains", expanded);
31966
+ return matchViaPointsAroundPlans(
31967
+ candidatePlans,
31968
+ /* @__PURE__ */ new Set([...promotedAlternatePlaneBusIds, ...expanded])
31969
+ );
31970
+ }
31971
+ }
31972
+ if (!result.selection)
31973
+ for (const index of (params.planeReservationRetryCount ?? 0) === 0 ? result.conflictDomainIndices : result.emptyDomainIndices.slice(0, 1))
31974
+ unroutablePlaneBusIds.add(
31975
+ candidateSets[index].planeBus.busId
31976
+ );
31977
+ } else {
31978
+ selectedCandidates = selectCompatiblePlaneRoutes(
31979
+ candidateSets,
31980
+ []
31981
+ );
31982
+ }
31268
31983
  alternatePlanePlans = selectedCandidates ? [
31269
31984
  ...feasibleAlternatePlanePlans,
31270
31985
  ...selectedCandidates.flatMap(
@@ -31288,6 +32003,15 @@ var FanoutSolver = class extends BaseSolver {
31288
32003
  );
31289
32004
  if (!alternatePlanePlans) return null;
31290
32005
  feasibleAlternatePlanePlans = alternatePlanePlans;
32006
+ if (useAdaptiveJointPlaneSelection) {
32007
+ matchedPlaneBusesInRoutingOrder = [];
32008
+ return new Map([
32009
+ ...fixedBoundaryViaPoints,
32010
+ ...alternatePlanePlans.filter((plan) => plan.via).map(
32011
+ (plan) => [plan.connectionIndex, plan.via.center]
32012
+ )
32013
+ ]);
32014
+ }
31291
32015
  }
31292
32016
  let planeBusIdsRoutedWithoutDogbones = /* @__PURE__ */ new Set();
31293
32017
  let allBlockingSegments = [...blockingSegments];
@@ -31679,6 +32403,30 @@ var FanoutSolver = class extends BaseSolver {
31679
32403
  return { plans: matchedPlans, failedBusIds: [] };
31680
32404
  }
31681
32405
  }
32406
+ if (useAdaptiveDensePlaneRouting && unroutablePlaneBusIds.size > 0 && (params.planeReservationRetryCount ?? 0) < 5) {
32407
+ const newlyPromoted = [...unroutablePlaneBusIds].filter(
32408
+ (id) => !activeBoundaryReservationPlaneBuses.some((bus) => bus.busId === id)
32409
+ );
32410
+ const refinedPromotions = (params.planeReservationRetryCount ?? 0) === 0 ? refineAdaptivePlaneReservationCore({
32411
+ candidateBusIds: newlyPromoted,
32412
+ activeBusIds: new Set(
32413
+ activeBoundaryReservationPlaneBuses.map((bus) => bus.busId)
32414
+ ),
32415
+ planeBuses
32416
+ }) : newlyPromoted;
32417
+ if (refinedPromotions.length > 0) {
32418
+ debugDense("plane-reservation-core", refinedPromotions);
32419
+ debugDense("promote-reservations", refinedPromotions);
32420
+ return yield* this.routeDenseThroughAllMixedTerminationSteps({
32421
+ ...params,
32422
+ promotedPlaneReservationBusIds: [
32423
+ ...params.promotedPlaneReservationBusIds ?? [],
32424
+ ...refinedPromotions
32425
+ ],
32426
+ planeReservationRetryCount: (params.planeReservationRetryCount ?? 0) + 1
32427
+ });
32428
+ }
32429
+ }
31682
32430
  if (matchLengthsAfterPlanes) {
31683
32431
  return yield* this.routeDenseThroughAllMixedTerminationSteps({
31684
32432
  ...params,
@@ -45341,7 +46089,7 @@ function Group_getRoutingPhasePlans(group) {
45341
46089
  var package_default = {
45342
46090
  name: "@tscircuit/core",
45343
46091
  type: "module",
45344
- version: "0.0.1831",
46092
+ version: "0.0.1833",
45345
46093
  types: "dist/index.d.ts",
45346
46094
  main: "dist/index.js",
45347
46095
  module: "dist/index.js",
@@ -45381,7 +46129,7 @@ var package_default = {
45381
46129
  "@tscircuit/common": "^0.0.20",
45382
46130
  "@tscircuit/copper-pour-solver": "^0.0.52",
45383
46131
  "@tscircuit/create-fdm-enclosure": "0.0.4",
45384
- "@tscircuit/fanout-solver": "0.0.54",
46132
+ "@tscircuit/fanout-solver": "0.0.56",
45385
46133
  "@tscircuit/footprinter": "^0.0.426",
45386
46134
  "@tscircuit/image-utils": "^0.0.8",
45387
46135
  "@tscircuit/implicit-copper-pour-solver": "github:tscircuit/implicit-copper-pour-solver#d880f80e4eb7a34d5da5a521f10a63c810d4728b",
@@ -50235,15 +50983,15 @@ import {
50235
50983
  } from "@tscircuit/copper-pour-solver";
50236
50984
 
50237
50985
  // lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
50238
- var EPSILON10 = 1e-9;
50986
+ var EPSILON11 = 1e-9;
50239
50987
  var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
50240
50988
  var isPointOnSegment = (p, a, b) => {
50241
50989
  const cross3 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
50242
- if (Math.abs(cross3) > EPSILON10) return false;
50990
+ if (Math.abs(cross3) > EPSILON11) return false;
50243
50991
  const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
50244
- if (dot < -EPSILON10) return false;
50992
+ if (dot < -EPSILON11) return false;
50245
50993
  const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
50246
- if (dot - squaredLength > EPSILON10) return false;
50994
+ if (dot - squaredLength > EPSILON11) return false;
50247
50995
  return true;
50248
50996
  };
50249
50997
  var isPointInRing = (point6, ring) => {
@@ -50267,7 +51015,7 @@ var isPointInRectPour = (p, pour) => {
50267
51015
  const dy = p.y - center.y;
50268
51016
  const localX = dx * cosR - dy * sinR;
50269
51017
  const localY = dx * sinR + dy * cosR;
50270
- return Math.abs(localX) <= width / 2 + EPSILON10 && Math.abs(localY) <= height / 2 + EPSILON10;
51018
+ return Math.abs(localX) <= width / 2 + EPSILON11 && Math.abs(localY) <= height / 2 + EPSILON11;
50271
51019
  };
50272
51020
  var isPointInBrepPour = (p, pour) => {
50273
51021
  const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
@@ -50301,7 +51049,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
50301
51049
  const dx = end.x - start.x;
50302
51050
  const dy = end.y - start.y;
50303
51051
  const length7 = Math.hypot(dx, dy);
50304
- if (length7 <= EPSILON10) return false;
51052
+ if (length7 <= EPSILON11) return false;
50305
51053
  const samples = [0, 0.25, 0.5, 0.75, 1];
50306
51054
  return samples.every(
50307
51055
  (t) => isPointInCopperPour(