@tscircuit/core 0.0.1651 → 0.0.1652

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 +1407 -261
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -17548,108 +17548,6 @@ function buildOutputSimpleRouteJson(params) {
17548
17548
  };
17549
17549
  }
17550
17550
 
17551
- // node_modules/@tscircuit/fanout-solver/lib/geometry.ts
17552
- var EPSILON = 1e-9;
17553
- function obstacleIsCircular(obstacle) {
17554
- return obstacle.shape === "circle";
17555
- }
17556
- function distance9(a, b) {
17557
- return Math.hypot(a.x - b.x, a.y - b.y);
17558
- }
17559
- function distancePointToSegment(point6, start, end) {
17560
- const dx = end.x - start.x;
17561
- const dy = end.y - start.y;
17562
- const lengthSquared = dx * dx + dy * dy;
17563
- const rawT = lengthSquared < EPSILON ? 0 : ((point6.x - start.x) * dx + (point6.y - start.y) * dy) / lengthSquared;
17564
- const t = Math.max(0, Math.min(1, rawT));
17565
- return Math.hypot(point6.x - (start.x + t * dx), point6.y - (start.y + t * dy));
17566
- }
17567
- function cross(origin, a, b) {
17568
- return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
17569
- }
17570
- function segmentsProperlyCross(a, b, c, d) {
17571
- const d1 = cross(c, d, a);
17572
- const d2 = cross(c, d, b);
17573
- const d3 = cross(a, b, c);
17574
- const d4 = cross(a, b, d);
17575
- return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
17576
- }
17577
- function distanceSegmentToSegment(firstStart, firstEnd, secondStart, secondEnd) {
17578
- if (segmentsProperlyCross(firstStart, firstEnd, secondStart, secondEnd)) {
17579
- return 0;
17580
- }
17581
- return Math.min(
17582
- distancePointToSegment(firstStart, secondStart, secondEnd),
17583
- distancePointToSegment(firstEnd, secondStart, secondEnd),
17584
- distancePointToSegment(secondStart, firstStart, firstEnd),
17585
- distancePointToSegment(secondEnd, firstStart, firstEnd)
17586
- );
17587
- }
17588
- function pointIsInsideObstacle(point6, obstacle, tolerance = EPSILON) {
17589
- if (obstacleIsCircular(obstacle)) {
17590
- return distance9(point6, obstacle.center) <= obstacle.width / 2 + tolerance;
17591
- }
17592
- return Math.abs(point6.x - obstacle.center.x) <= obstacle.width / 2 + tolerance && Math.abs(point6.y - obstacle.center.y) <= obstacle.height / 2 + tolerance;
17593
- }
17594
- function distancePointToObstacle(point6, obstacle) {
17595
- if (obstacleIsCircular(obstacle)) {
17596
- return Math.max(0, distance9(point6, obstacle.center) - obstacle.width / 2);
17597
- }
17598
- const dx = Math.max(
17599
- Math.abs(point6.x - obstacle.center.x) - obstacle.width / 2,
17600
- 0
17601
- );
17602
- const dy = Math.max(
17603
- Math.abs(point6.y - obstacle.center.y) - obstacle.height / 2,
17604
- 0
17605
- );
17606
- return Math.hypot(dx, dy);
17607
- }
17608
- function distanceSegmentToObstacle(segment2, obstacle) {
17609
- if (obstacleIsCircular(obstacle)) {
17610
- return Math.max(
17611
- 0,
17612
- distancePointToSegment(obstacle.center, segment2.start, segment2.end) - obstacle.width / 2
17613
- );
17614
- }
17615
- if (pointIsInsideObstacle(segment2.start, obstacle) || pointIsInsideObstacle(segment2.end, obstacle)) {
17616
- return 0;
17617
- }
17618
- const minX = obstacle.center.x - obstacle.width / 2;
17619
- const maxX = obstacle.center.x + obstacle.width / 2;
17620
- const minY = obstacle.center.y - obstacle.height / 2;
17621
- const maxY = obstacle.center.y + obstacle.height / 2;
17622
- const corners = [
17623
- { x: minX, y: minY },
17624
- { x: maxX, y: minY },
17625
- { x: maxX, y: maxY },
17626
- { x: minX, y: maxY }
17627
- ];
17628
- let minimumDistance = Number.POSITIVE_INFINITY;
17629
- for (let index = 0; index < corners.length; index++) {
17630
- minimumDistance = Math.min(
17631
- minimumDistance,
17632
- distanceSegmentToSegment(
17633
- segment2.start,
17634
- segment2.end,
17635
- corners[index],
17636
- corners[(index + 1) % corners.length]
17637
- )
17638
- );
17639
- }
17640
- return minimumDistance;
17641
- }
17642
- function segmentsAreClear(first, second, clearance) {
17643
- if (first.layer !== second.layer) return true;
17644
- const requiredDistance = (first.width + second.width) / 2 + clearance;
17645
- return distanceSegmentToSegment(
17646
- first.start,
17647
- first.end,
17648
- second.start,
17649
- second.end
17650
- ) >= requiredDistance - EPSILON;
17651
- }
17652
-
17653
17551
  // node_modules/@tscircuit/fanout-solver/lib/layer-colors.ts
17654
17552
  var COPPER_LAYER_COLORS = [
17655
17553
  "#ef4444",
@@ -17703,7 +17601,7 @@ function getLayerSpan(fromLayer, toLayer, layerNames) {
17703
17601
  return layerNames.slice(firstIndex, lastIndex + 1);
17704
17602
  }
17705
17603
  function generateLayerAssignments(params) {
17706
- const { busIds, layers, maxAssignments } = params;
17604
+ const { busIds, layers, layersByBusId, maxAssignments } = params;
17707
17605
  if (layers.length === 0) {
17708
17606
  throw new Error("FanoutSolver: no escape layers are available");
17709
17607
  }
@@ -17712,7 +17610,21 @@ function generateLayerAssignments(params) {
17712
17610
  `FanoutSolver: maxLayerCombinations must be positive, received ${maxAssignments}`
17713
17611
  );
17714
17612
  }
17715
- const rawCombinationCount = layers.length ** busIds.length;
17613
+ const availableLayersByBus = busIds.map(
17614
+ (busId) => layersByBusId?.[busId] ?? layers
17615
+ );
17616
+ const busWithoutLayersIndex = availableLayersByBus.findIndex(
17617
+ (availableLayers) => availableLayers.length === 0
17618
+ );
17619
+ if (busWithoutLayersIndex >= 0) {
17620
+ throw new Error(
17621
+ `FanoutSolver: no escape layers are available for bus "${busIds[busWithoutLayersIndex]}"`
17622
+ );
17623
+ }
17624
+ const rawCombinationCount = availableLayersByBus.reduce(
17625
+ (count, availableLayers) => count * availableLayers.length,
17626
+ 1
17627
+ );
17716
17628
  const combinationCount = Math.min(
17717
17629
  maxAssignments,
17718
17630
  Number.isFinite(rawCombinationCount) ? rawCombinationCount : maxAssignments
@@ -17722,7 +17634,7 @@ function generateLayerAssignments(params) {
17722
17634
  function addAssignment(layerIndexes) {
17723
17635
  const assignment = {};
17724
17636
  for (let busIndex = 0; busIndex < busIds.length; busIndex++) {
17725
- assignment[busIds[busIndex]] = layers[layerIndexes[busIndex]];
17637
+ assignment[busIds[busIndex]] = availableLayersByBus[busIndex][layerIndexes[busIndex]];
17726
17638
  }
17727
17639
  const key = JSON.stringify(assignment);
17728
17640
  if (seenAssignments.has(key)) return;
@@ -17734,9 +17646,10 @@ function generateLayerAssignments(params) {
17734
17646
  const layerIndexes = [];
17735
17647
  let remaining = ordinal;
17736
17648
  for (let busIndex = 0; busIndex < busIds.length; busIndex++) {
17737
- const digit = remaining % layers.length;
17738
- remaining = Math.floor(remaining / layers.length);
17739
- layerIndexes.push((digit + busIndex) % layers.length);
17649
+ const layerCount = availableLayersByBus[busIndex].length;
17650
+ const digit = remaining % layerCount;
17651
+ remaining = Math.floor(remaining / layerCount);
17652
+ layerIndexes.push((digit + busIndex) % layerCount);
17740
17653
  }
17741
17654
  addAssignment(layerIndexes);
17742
17655
  }
@@ -17749,33 +17662,148 @@ function generateLayerAssignments(params) {
17749
17662
  return (mixed ^ mixed >>> 15) >>> 0;
17750
17663
  }
17751
17664
  const balancedLayerIndexes = busIds.map(
17752
- (_, busIndex) => busIndex % layers.length
17665
+ (_, busIndex) => busIndex % availableLayersByBus[busIndex].length
17753
17666
  );
17754
17667
  addAssignment(balancedLayerIndexes);
17755
- for (let globalShift = 1; globalShift < layers.length && assignments.length < combinationCount; globalShift++) {
17668
+ const maximumAvailableLayerCount = Math.max(
17669
+ ...availableLayersByBus.map((availableLayers) => availableLayers.length)
17670
+ );
17671
+ for (let globalShift = 1; globalShift < maximumAvailableLayerCount && assignments.length < combinationCount; globalShift++) {
17756
17672
  addAssignment(
17757
17673
  balancedLayerIndexes.map(
17758
- (layerIndex) => (layerIndex + globalShift) % layers.length
17674
+ (layerIndex, busIndex) => (layerIndex + globalShift) % availableLayersByBus[busIndex].length
17759
17675
  )
17760
17676
  );
17761
17677
  }
17762
17678
  for (let busIndex = 0; busIndex < busIds.length && assignments.length < combinationCount; busIndex++) {
17763
- for (let shift = 1; shift < layers.length && assignments.length < combinationCount; shift++) {
17679
+ for (let shift = 1; shift < availableLayersByBus[busIndex].length && assignments.length < combinationCount; shift++) {
17764
17680
  const layerIndexes = [...balancedLayerIndexes];
17765
- layerIndexes[busIndex] = (balancedLayerIndexes[busIndex] + shift) % layers.length;
17681
+ layerIndexes[busIndex] = (balancedLayerIndexes[busIndex] + shift) % availableLayersByBus[busIndex].length;
17766
17682
  addAssignment(layerIndexes);
17767
17683
  }
17768
17684
  }
17769
17685
  for (let seed = 1; assignments.length < combinationCount && seed < combinationCount * 20; seed++) {
17770
17686
  addAssignment(
17771
17687
  busIds.map(
17772
- (_, busIndex) => mix32(seed * 2654435761 + busIndex * 2246822507) % layers.length
17688
+ (_, busIndex) => mix32(seed * 2654435761 + busIndex * 2246822507) % availableLayersByBus[busIndex].length
17773
17689
  )
17774
17690
  );
17775
17691
  }
17776
17692
  return assignments;
17777
17693
  }
17778
17694
 
17695
+ // node_modules/@tscircuit/fanout-solver/lib/geometry.ts
17696
+ var EPSILON = 1e-9;
17697
+ function obstacleIsCircular(obstacle) {
17698
+ return obstacle.shape === "circle";
17699
+ }
17700
+ function toObstacleLocalPoint(point6, obstacle) {
17701
+ const rotationRadians = -(obstacle.ccwRotationDegrees ?? 0) * Math.PI / 180;
17702
+ const dx = point6.x - obstacle.center.x;
17703
+ const dy = point6.y - obstacle.center.y;
17704
+ return {
17705
+ x: dx * Math.cos(rotationRadians) - dy * Math.sin(rotationRadians),
17706
+ y: dx * Math.sin(rotationRadians) + dy * Math.cos(rotationRadians)
17707
+ };
17708
+ }
17709
+ function distance9(a, b) {
17710
+ return Math.hypot(a.x - b.x, a.y - b.y);
17711
+ }
17712
+ function distancePointToSegment(point6, start, end) {
17713
+ const dx = end.x - start.x;
17714
+ const dy = end.y - start.y;
17715
+ const lengthSquared = dx * dx + dy * dy;
17716
+ const rawT = lengthSquared < EPSILON ? 0 : ((point6.x - start.x) * dx + (point6.y - start.y) * dy) / lengthSquared;
17717
+ const t = Math.max(0, Math.min(1, rawT));
17718
+ return Math.hypot(point6.x - (start.x + t * dx), point6.y - (start.y + t * dy));
17719
+ }
17720
+ function cross(origin, a, b) {
17721
+ return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
17722
+ }
17723
+ function segmentsProperlyCross(a, b, c, d) {
17724
+ const d1 = cross(c, d, a);
17725
+ const d2 = cross(c, d, b);
17726
+ const d3 = cross(a, b, c);
17727
+ const d4 = cross(a, b, d);
17728
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
17729
+ }
17730
+ function distanceSegmentToSegment(firstStart, firstEnd, secondStart, secondEnd) {
17731
+ if (segmentsProperlyCross(firstStart, firstEnd, secondStart, secondEnd)) {
17732
+ return 0;
17733
+ }
17734
+ return Math.min(
17735
+ distancePointToSegment(firstStart, secondStart, secondEnd),
17736
+ distancePointToSegment(firstEnd, secondStart, secondEnd),
17737
+ distancePointToSegment(secondStart, firstStart, firstEnd),
17738
+ distancePointToSegment(secondEnd, firstStart, firstEnd)
17739
+ );
17740
+ }
17741
+ function pointIsInsideObstacle(point6, obstacle, tolerance = EPSILON) {
17742
+ if (obstacleIsCircular(obstacle)) {
17743
+ return distance9(point6, obstacle.center) <= obstacle.width / 2 + tolerance;
17744
+ }
17745
+ const localPoint = toObstacleLocalPoint(point6, obstacle);
17746
+ return Math.abs(localPoint.x) <= obstacle.width / 2 + tolerance && Math.abs(localPoint.y) <= obstacle.height / 2 + tolerance;
17747
+ }
17748
+ function distancePointToObstacle(point6, obstacle) {
17749
+ if (obstacleIsCircular(obstacle)) {
17750
+ return Math.max(0, distance9(point6, obstacle.center) - obstacle.width / 2);
17751
+ }
17752
+ const localPoint = toObstacleLocalPoint(point6, obstacle);
17753
+ const dx = Math.max(Math.abs(localPoint.x) - obstacle.width / 2, 0);
17754
+ const dy = Math.max(Math.abs(localPoint.y) - obstacle.height / 2, 0);
17755
+ return Math.hypot(dx, dy);
17756
+ }
17757
+ function distanceSegmentToObstacle(segment2, obstacle) {
17758
+ if (obstacleIsCircular(obstacle)) {
17759
+ return Math.max(
17760
+ 0,
17761
+ distancePointToSegment(obstacle.center, segment2.start, segment2.end) - obstacle.width / 2
17762
+ );
17763
+ }
17764
+ const localStart = toObstacleLocalPoint(segment2.start, obstacle);
17765
+ const localEnd = toObstacleLocalPoint(segment2.end, obstacle);
17766
+ if (Math.abs(localStart.x) <= obstacle.width / 2 + EPSILON && Math.abs(localStart.y) <= obstacle.height / 2 + EPSILON) {
17767
+ return 0;
17768
+ }
17769
+ if (Math.abs(localEnd.x) <= obstacle.width / 2 + EPSILON && Math.abs(localEnd.y) <= obstacle.height / 2 + EPSILON) {
17770
+ return 0;
17771
+ }
17772
+ const minX = -obstacle.width / 2;
17773
+ const maxX = obstacle.width / 2;
17774
+ const minY = -obstacle.height / 2;
17775
+ const maxY = obstacle.height / 2;
17776
+ const corners = [
17777
+ { x: minX, y: minY },
17778
+ { x: maxX, y: minY },
17779
+ { x: maxX, y: maxY },
17780
+ { x: minX, y: maxY }
17781
+ ];
17782
+ let minimumDistance = Number.POSITIVE_INFINITY;
17783
+ for (let index = 0; index < corners.length; index++) {
17784
+ minimumDistance = Math.min(
17785
+ minimumDistance,
17786
+ distanceSegmentToSegment(
17787
+ localStart,
17788
+ localEnd,
17789
+ corners[index],
17790
+ corners[(index + 1) % corners.length]
17791
+ )
17792
+ );
17793
+ }
17794
+ return minimumDistance;
17795
+ }
17796
+ function segmentsAreClear(first, second, clearance) {
17797
+ if (first.layer !== second.layer) return true;
17798
+ const requiredDistance = (first.width + second.width) / 2 + clearance;
17799
+ return distanceSegmentToSegment(
17800
+ first.start,
17801
+ first.end,
17802
+ second.start,
17803
+ second.end
17804
+ ) >= requiredDistance - EPSILON;
17805
+ }
17806
+
17779
17807
  // node_modules/@tscircuit/fanout-solver/lib/prepare-buses.ts
17780
17808
  var FANOUT_BORDER_TARGETS = /* @__PURE__ */ new Set([
17781
17809
  "left",
@@ -18525,6 +18553,122 @@ function prepareFanoutBuses(srj, options) {
18525
18553
  return buses;
18526
18554
  }
18527
18555
 
18556
+ // node_modules/@tscircuit/fanout-solver/lib/net-identity.ts
18557
+ var identityCache = /* @__PURE__ */ new WeakMap();
18558
+ function getConnectionNetKey(connection) {
18559
+ return connection.netConnectionName ?? connection.rootConnectionName ?? connection.name;
18560
+ }
18561
+ function addTokenNet(tokenNetKeys, token, netKey) {
18562
+ if (!token) return false;
18563
+ const keys = tokenNetKeys.get(token) ?? /* @__PURE__ */ new Set();
18564
+ const sizeBefore = keys.size;
18565
+ keys.add(netKey);
18566
+ tokenNetKeys.set(token, keys);
18567
+ return keys.size !== sizeBefore;
18568
+ }
18569
+ function getKnownNetKeys(tokenNetKeys, tokens) {
18570
+ const keys = /* @__PURE__ */ new Set();
18571
+ for (const token of tokens) {
18572
+ for (const key of tokenNetKeys.get(token) ?? []) keys.add(key);
18573
+ }
18574
+ return keys;
18575
+ }
18576
+ function createElectricalNetIdentity(srj) {
18577
+ const connectionNetKeys = /* @__PURE__ */ new Map();
18578
+ const tokenNetKeys = /* @__PURE__ */ new Map();
18579
+ for (const connection of srj.connections) {
18580
+ const netKey = getConnectionNetKey(connection);
18581
+ connectionNetKeys.set(connection.name, netKey);
18582
+ addTokenNet(tokenNetKeys, connection.name, netKey);
18583
+ addTokenNet(tokenNetKeys, connection.rootConnectionName, netKey);
18584
+ addTokenNet(tokenNetKeys, connection.netConnectionName, netKey);
18585
+ for (const point6 of connection.pointsToConnect) {
18586
+ addTokenNet(tokenNetKeys, point6.pointId, netKey);
18587
+ addTokenNet(tokenNetKeys, point6.pcb_port_id, netKey);
18588
+ }
18589
+ }
18590
+ for (const trace of srj.traces ?? []) {
18591
+ const netKey = trace.connection_name ? connectionNetKeys.get(trace.connection_name) : void 0;
18592
+ if (!netKey) continue;
18593
+ addTokenNet(tokenNetKeys, trace.pcb_trace_id, netKey);
18594
+ for (const token of trace.connectsTo ?? []) {
18595
+ addTokenNet(tokenNetKeys, token, netKey);
18596
+ }
18597
+ }
18598
+ for (let pass = 0; pass < 2; pass++) {
18599
+ let changed = false;
18600
+ for (const obstacle of srj.obstacles) {
18601
+ const netKeys = getKnownNetKeys(tokenNetKeys, obstacle.connectedTo);
18602
+ if (netKeys.size !== 1) continue;
18603
+ const netKey = [...netKeys][0];
18604
+ for (const token of obstacle.connectedTo) {
18605
+ changed = addTokenNet(tokenNetKeys, token, netKey) || changed;
18606
+ }
18607
+ }
18608
+ if (!changed) break;
18609
+ }
18610
+ const parentByNetKey = /* @__PURE__ */ new Map();
18611
+ const findRoot = (netKey) => {
18612
+ const parent = parentByNetKey.get(netKey) ?? netKey;
18613
+ parentByNetKey.set(netKey, parent);
18614
+ if (parent === netKey) return netKey;
18615
+ const root = findRoot(parent);
18616
+ parentByNetKey.set(netKey, root);
18617
+ return root;
18618
+ };
18619
+ const union = (first, second) => {
18620
+ const firstRoot = findRoot(first);
18621
+ const secondRoot = findRoot(second);
18622
+ if (firstRoot !== secondRoot) parentByNetKey.set(secondRoot, firstRoot);
18623
+ };
18624
+ for (const netKeys of tokenNetKeys.values()) {
18625
+ const [firstNetKey, ...otherNetKeys] = [...netKeys];
18626
+ if (!firstNetKey) continue;
18627
+ for (const otherNetKey of otherNetKeys) union(firstNetKey, otherNetKey);
18628
+ }
18629
+ for (const connectedTokens of [
18630
+ ...srj.obstacles.map((obstacle) => obstacle.connectedTo),
18631
+ ...(srj.traces ?? []).map((trace) => trace.connectsTo ?? [])
18632
+ ]) {
18633
+ const [firstNetKey, ...otherNetKeys] = [
18634
+ ...getKnownNetKeys(tokenNetKeys, connectedTokens)
18635
+ ];
18636
+ if (!firstNetKey) continue;
18637
+ for (const otherNetKey of otherNetKeys) union(firstNetKey, otherNetKey);
18638
+ }
18639
+ for (const [connectionName, netKey] of connectionNetKeys) {
18640
+ connectionNetKeys.set(connectionName, findRoot(netKey));
18641
+ }
18642
+ for (const [token, netKeys] of tokenNetKeys) {
18643
+ tokenNetKeys.set(
18644
+ token,
18645
+ new Set([...netKeys].map((netKey) => findRoot(netKey)))
18646
+ );
18647
+ }
18648
+ return { connectionNetKeys, tokenNetKeys };
18649
+ }
18650
+ function getElectricalNetIdentity(srj) {
18651
+ const cached = identityCache.get(srj);
18652
+ if (cached) return cached;
18653
+ const identity9 = createElectricalNetIdentity(srj);
18654
+ identityCache.set(srj, identity9);
18655
+ return identity9;
18656
+ }
18657
+ function connectionsShareElectricalNet(srj, firstConnectionName, secondConnectionName) {
18658
+ const identity9 = getElectricalNetIdentity(srj);
18659
+ const firstNet = identity9.connectionNetKeys.get(firstConnectionName);
18660
+ const secondNet = identity9.connectionNetKeys.get(secondConnectionName);
18661
+ return firstNet !== void 0 && firstNet === secondNet;
18662
+ }
18663
+ function obstacleSharesElectricalNet(srj, obstacle, connectionName) {
18664
+ const identity9 = getElectricalNetIdentity(srj);
18665
+ const connectionNet = identity9.connectionNetKeys.get(connectionName);
18666
+ if (!connectionNet) return false;
18667
+ return obstacle.connectedTo.some(
18668
+ (token) => identity9.tokenNetKeys.get(token)?.has(connectionNet)
18669
+ );
18670
+ }
18671
+
18528
18672
  // node_modules/@tscircuit/fanout-solver/lib/route-bus.ts
18529
18673
  function isHorizontal(direction) {
18530
18674
  return direction === "left" || direction === "right";
@@ -18935,10 +19079,21 @@ function buildPlan(params) {
18935
19079
  };
18936
19080
  }
18937
19081
  function segmentIsClearOfObstacles(params) {
18938
- const { segment: segment2, plan, segmentIndex, obstacles, clearance } = params;
19082
+ const {
19083
+ segment: segment2,
19084
+ plan,
19085
+ segmentIndex,
19086
+ srj,
19087
+ allowSameNetMerges,
19088
+ obstacles,
19089
+ clearance
19090
+ } = params;
18939
19091
  for (const obstacle of obstacles) {
18940
19092
  if (!obstacle.layers.includes(segment2.layer)) continue;
18941
19093
  if (obstacle.connectedTo.includes(plan.connectionName)) continue;
19094
+ if (allowSameNetMerges && obstacleSharesElectricalNet(srj, obstacle, plan.connectionName)) {
19095
+ continue;
19096
+ }
18942
19097
  if (segmentIndex === 0 && obstacle === plan.sourceObstacle && segment2.layer === plan.sourceLayer) {
18943
19098
  continue;
18944
19099
  }
@@ -18948,8 +19103,8 @@ function segmentIsClearOfObstacles(params) {
18948
19103
  }
18949
19104
  return true;
18950
19105
  }
18951
- function planIsClear(params) {
18952
- const { plan, otherPlans, srj, sharedBoundary, clearance } = params;
19106
+ function planIsStaticallyClear(params) {
19107
+ const { plan, srj, sharedBoundary, clearance, allowSameNetMerges } = params;
18953
19108
  const routableBounds = getRoutableBounds(srj.bounds, sharedBoundary);
18954
19109
  if (!pointIsInsideBounds(plan.exitPoint, routableBounds) || plan.segments.some(
18955
19110
  (segment2) => !pointIsInsideBounds(segment2.start, routableBounds) || !pointIsInsideBounds(segment2.end, routableBounds)
@@ -18961,6 +19116,8 @@ function planIsClear(params) {
18961
19116
  segment: plan.segments[index],
18962
19117
  plan,
18963
19118
  segmentIndex: index,
19119
+ srj,
19120
+ allowSameNetMerges,
18964
19121
  obstacles: srj.obstacles,
18965
19122
  clearance
18966
19123
  })) {
@@ -18972,21 +19129,55 @@ function planIsClear(params) {
18972
19129
  if (!obstacle.layers.some((layer) => plan.via.spanLayers.includes(layer))) {
18973
19130
  continue;
18974
19131
  }
19132
+ if (allowSameNetMerges && obstacleSharesElectricalNet(srj, obstacle, plan.connectionName)) {
19133
+ continue;
19134
+ }
18975
19135
  if (distancePointToObstacle(plan.via.center, obstacle) < plan.via.diameter / 2 + clearance - 1e-9) {
18976
19136
  return false;
18977
19137
  }
18978
19138
  }
18979
19139
  }
19140
+ return true;
19141
+ }
19142
+ function planIsClearOfPlans(params) {
19143
+ const {
19144
+ plan,
19145
+ otherPlans,
19146
+ srj,
19147
+ allowSameNetMerges,
19148
+ clearance,
19149
+ blockingBusCounts
19150
+ } = params;
18980
19151
  for (const otherPlan of otherPlans) {
19152
+ if (allowSameNetMerges && connectionsShareElectricalNet(
19153
+ srj,
19154
+ plan.connectionName,
19155
+ otherPlan.connectionName
19156
+ )) {
19157
+ continue;
19158
+ }
19159
+ const plansShareSourcePort = plan.sourcePoint.pcb_port_id && plan.sourcePoint.pcb_port_id === otherPlan.sourcePoint.pcb_port_id || plan.sourcePoint.pointId && plan.sourcePoint.pointId === otherPlan.sourcePoint.pointId;
19160
+ if (plansShareSourcePort) continue;
19161
+ const recordBlocker = () => {
19162
+ if (otherPlan.busId === plan.busId) return;
19163
+ blockingBusCounts?.set(
19164
+ otherPlan.busId,
19165
+ (blockingBusCounts.get(otherPlan.busId) ?? 0) + 1
19166
+ );
19167
+ };
18981
19168
  for (const segment2 of plan.segments) {
18982
19169
  for (const otherSegment of otherPlan.segments) {
18983
- if (!segmentsAreClear(segment2, otherSegment, clearance)) return false;
19170
+ if (!segmentsAreClear(segment2, otherSegment, clearance)) {
19171
+ recordBlocker();
19172
+ return false;
19173
+ }
18984
19174
  }
18985
19175
  if (otherPlan.via?.spanLayers.includes(segment2.layer) && distancePointToSegment(
18986
19176
  otherPlan.via.center,
18987
19177
  segment2.start,
18988
19178
  segment2.end
18989
19179
  ) < otherPlan.via.diameter / 2 + segment2.width / 2 + clearance - 1e-9) {
19180
+ recordBlocker();
18990
19181
  return false;
18991
19182
  }
18992
19183
  }
@@ -18997,18 +19188,52 @@ function planIsClear(params) {
18997
19188
  otherSegment.start,
18998
19189
  otherSegment.end
18999
19190
  ) < plan.via.diameter / 2 + otherSegment.width / 2 + clearance - 1e-9) {
19191
+ recordBlocker();
19000
19192
  return false;
19001
19193
  }
19002
19194
  }
19003
19195
  if (otherPlan.via && plan.via.spanLayers.some(
19004
19196
  (layer) => otherPlan.via.spanLayers.includes(layer)
19005
19197
  ) && distance9(plan.via.center, otherPlan.via.center) < (plan.via.diameter + otherPlan.via.diameter) / 2 + clearance - 1e-9) {
19198
+ recordBlocker();
19006
19199
  return false;
19007
19200
  }
19008
19201
  }
19009
19202
  }
19010
19203
  return true;
19011
19204
  }
19205
+ function planIsClear(params) {
19206
+ const {
19207
+ plan,
19208
+ otherPlans,
19209
+ staticClearanceCache,
19210
+ blockingBusCounts,
19211
+ cacheKey,
19212
+ srj,
19213
+ sharedBoundary,
19214
+ clearance,
19215
+ allowSameNetMerges
19216
+ } = params;
19217
+ let staticallyClear = staticClearanceCache?.get(cacheKey);
19218
+ if (staticallyClear === void 0) {
19219
+ staticallyClear = planIsStaticallyClear({
19220
+ plan,
19221
+ srj,
19222
+ sharedBoundary,
19223
+ clearance,
19224
+ allowSameNetMerges
19225
+ });
19226
+ staticClearanceCache?.set(cacheKey, staticallyClear);
19227
+ }
19228
+ return staticallyClear && planIsClearOfPlans({
19229
+ plan,
19230
+ otherPlans,
19231
+ srj,
19232
+ allowSameNetMerges,
19233
+ clearance,
19234
+ blockingBusCounts
19235
+ });
19236
+ }
19012
19237
  function routePlaneTerminatedBus(params) {
19013
19238
  const {
19014
19239
  srj,
@@ -19019,7 +19244,10 @@ function routePlaneTerminatedBus(params) {
19019
19244
  traceWidth,
19020
19245
  viaDiameter,
19021
19246
  viaHoleDiameter,
19022
- clearance
19247
+ clearance,
19248
+ staticClearanceCache,
19249
+ blockingBusCounts,
19250
+ allowSameNetMerges = false
19023
19251
  } = params;
19024
19252
  const sourceObstacle = bus.connections[0]?.sourceObstacle;
19025
19253
  if (!sourceObstacle || bus.termination.type !== "plane") return null;
@@ -19056,9 +19284,13 @@ function routePlaneTerminatedBus(params) {
19056
19284
  if (!planIsClear({
19057
19285
  plan,
19058
19286
  otherPlans: [...acceptedPlans, ...candidatePlans],
19287
+ staticClearanceCache,
19288
+ blockingBusCounts,
19289
+ cacheKey: `plane:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}`,
19059
19290
  srj,
19060
19291
  sharedBoundary: bus.sharedBoundary,
19061
- clearance
19292
+ clearance,
19293
+ allowSameNetMerges
19062
19294
  })) {
19063
19295
  orderIsClear = false;
19064
19296
  break;
@@ -19070,7 +19302,7 @@ function routePlaneTerminatedBus(params) {
19070
19302
  }
19071
19303
  return null;
19072
19304
  }
19073
- function routeBus(params) {
19305
+ function routeBusAlternatives(params, maxAlternatives = 1) {
19074
19306
  const {
19075
19307
  srj,
19076
19308
  bus,
@@ -19081,14 +19313,23 @@ function routeBus(params) {
19081
19313
  viaDiameter,
19082
19314
  viaHoleDiameter,
19083
19315
  clearance,
19084
- compactBusTracks
19316
+ compactBusTracks,
19317
+ staticClearanceCache,
19318
+ blockingBusCounts,
19319
+ allowSameNetMerges = false
19085
19320
  } = params;
19086
- if (bus.termination.type === "plane") {
19087
- return routePlaneTerminatedBus(params);
19088
- }
19321
+ if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
19322
+ throw new Error(
19323
+ `FanoutSolver: maxAlternatives must be a positive integer, received ${maxAlternatives}`
19324
+ );
19325
+ }
19326
+ if (bus.termination.type === "plane") {
19327
+ const plan = routePlaneTerminatedBus(params);
19328
+ return plan ? [plan] : [];
19329
+ }
19089
19330
  const exitAxis = getExitAxis(bus);
19090
19331
  const sourceObstacle = bus.connections[0]?.sourceObstacle;
19091
- if (!sourceObstacle) return [];
19332
+ if (!sourceObstacle) return [[]];
19092
19333
  const directionalPadSize = isHorizontal(bus.direction) ? sourceObstacle.width : sourceObstacle.height;
19093
19334
  const sourceLayer = bus.connections[0].sourceLayer;
19094
19335
  const targetUsesVia = targetLayer !== sourceLayer;
@@ -19096,68 +19337,94 @@ function routeBus(params) {
19096
19337
  const pairChannelFitsVia = getDirectionalPitch(bus) / 2 - directionalPadSize / 2 >= viaDiameter / 2 + clearance - 1e-9;
19097
19338
  const interstitialEscape = targetUsesVia && !outwardEdgeBus && !pairChannelFitsVia;
19098
19339
  const viaHandednesses = targetUsesVia ? pairChannelFitsVia || outwardEdgeBus ? [0] : [1, -1] : [0];
19340
+ const alternatives = [];
19341
+ const seenAlternativeKeys = /* @__PURE__ */ new Set();
19342
+ const addAlternative = (plans) => {
19343
+ const key = plans.map(
19344
+ (plan) => `${plan.connectionIndex}:${plan.targetLayer}:${plan.exitPoint.x}:${plan.exitPoint.y}:${plan.segments.map((segment2) => `${segment2.start.x},${segment2.start.y},${segment2.end.x},${segment2.end.y},${segment2.layer}`).join(";")}`
19345
+ ).join("|");
19346
+ if (seenAlternativeKeys.has(key)) return;
19347
+ seenAlternativeKeys.add(key);
19348
+ alternatives.push(plans);
19349
+ };
19350
+ const searchConnectionOrder = (connectionOrder, viaHandedness, connectionIndex, candidatePlans) => {
19351
+ if (alternatives.length >= maxAlternatives) return;
19352
+ if (connectionIndex >= connectionOrder.length) {
19353
+ addAlternative(candidatePlans);
19354
+ return;
19355
+ }
19356
+ const preparedConnection = connectionOrder[connectionIndex];
19357
+ const connectionRank = getConnectionRank(bus, preparedConnection);
19358
+ const trackCandidates = getTrackCandidates({
19359
+ bus,
19360
+ connection: preparedConnection,
19361
+ preferredTrack: getPreferredTrack({
19362
+ bus,
19363
+ connection: preparedConnection,
19364
+ targetUsesVia,
19365
+ interstitialEscape,
19366
+ compactBusTracks,
19367
+ traceWidth,
19368
+ viaDiameter,
19369
+ clearance
19370
+ }),
19371
+ traceWidth,
19372
+ clearance
19373
+ });
19374
+ for (let trackIndex = 0; trackIndex < trackCandidates.length; trackIndex++) {
19375
+ const track = trackCandidates[trackIndex];
19376
+ const plan = buildPlan({
19377
+ preparedConnection,
19378
+ bus,
19379
+ targetLayer,
19380
+ track: track.value,
19381
+ exitAxis,
19382
+ layerNames,
19383
+ traceWidth,
19384
+ viaDiameter,
19385
+ viaHoleDiameter,
19386
+ viaHandedness,
19387
+ interstitialEscape,
19388
+ spreadLaneIndex: Math.min(
19389
+ connectionRank,
19390
+ bus.connections.length - connectionRank - 1
19391
+ ),
19392
+ clearance,
19393
+ terminateAtVia: false
19394
+ });
19395
+ if (!planIsClear({
19396
+ plan,
19397
+ otherPlans: [...acceptedPlans, ...candidatePlans],
19398
+ staticClearanceCache,
19399
+ blockingBusCounts,
19400
+ cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}`,
19401
+ srj,
19402
+ sharedBoundary: bus.sharedBoundary,
19403
+ clearance,
19404
+ allowSameNetMerges
19405
+ })) {
19406
+ continue;
19407
+ }
19408
+ searchConnectionOrder(
19409
+ connectionOrder,
19410
+ viaHandedness,
19411
+ connectionIndex + 1,
19412
+ [...candidatePlans, plan]
19413
+ );
19414
+ if (alternatives.length >= maxAlternatives) return;
19415
+ if (maxAlternatives === 1) return;
19416
+ }
19417
+ };
19099
19418
  for (const viaHandedness of viaHandednesses) {
19100
19419
  for (const connectionOrder of getConnectionOrders(bus)) {
19101
- const candidatePlans = [];
19102
- let orderIsClear = true;
19103
- for (const preparedConnection of connectionOrder) {
19104
- let acceptedPlan = null;
19105
- for (const track of getTrackCandidates({
19106
- bus,
19107
- connection: preparedConnection,
19108
- preferredTrack: getPreferredTrack({
19109
- bus,
19110
- connection: preparedConnection,
19111
- targetUsesVia,
19112
- interstitialEscape,
19113
- compactBusTracks,
19114
- traceWidth,
19115
- viaDiameter,
19116
- clearance
19117
- }),
19118
- traceWidth,
19119
- clearance
19120
- })) {
19121
- const plan = buildPlan({
19122
- preparedConnection,
19123
- bus,
19124
- targetLayer,
19125
- track: track.value,
19126
- exitAxis,
19127
- layerNames,
19128
- traceWidth,
19129
- viaDiameter,
19130
- viaHoleDiameter,
19131
- viaHandedness,
19132
- interstitialEscape,
19133
- spreadLaneIndex: Math.min(
19134
- getConnectionRank(bus, preparedConnection),
19135
- bus.connections.length - getConnectionRank(bus, preparedConnection) - 1
19136
- ),
19137
- clearance,
19138
- terminateAtVia: false
19139
- });
19140
- if (planIsClear({
19141
- plan,
19142
- otherPlans: [...acceptedPlans, ...candidatePlans],
19143
- srj,
19144
- sharedBoundary: bus.sharedBoundary,
19145
- clearance
19146
- })) {
19147
- acceptedPlan = plan;
19148
- break;
19149
- }
19150
- }
19151
- if (!acceptedPlan) {
19152
- orderIsClear = false;
19153
- break;
19154
- }
19155
- candidatePlans.push(acceptedPlan);
19156
- }
19157
- if (orderIsClear) return candidatePlans;
19420
+ searchConnectionOrder(connectionOrder, viaHandedness, 0, []);
19421
+ if (alternatives.length >= maxAlternatives) return alternatives;
19158
19422
  }
19159
19423
  }
19160
- return null;
19424
+ return alternatives;
19425
+ }
19426
+ function routeBus(params) {
19427
+ return routeBusAlternatives(params, 1)[0] ?? null;
19161
19428
  }
19162
19429
 
19163
19430
  // node_modules/@tscircuit/fanout-solver/lib/route-single-layer-adaptive-exits.ts
@@ -20978,6 +21245,539 @@ function routeSingleLayerWithPushAndShove(params) {
20978
21245
  return paths.map(buildPlan3);
20979
21246
  }
20980
21247
 
21248
+ // node_modules/@tscircuit/fanout-solver/lib/validate-fanout-solution.ts
21249
+ var EPSILON3 = 1e-6;
21250
+ function pointsMatch(first, second) {
21251
+ return distance9(first, second) <= EPSILON3;
21252
+ }
21253
+ function getPointLayers2(point6) {
21254
+ return "layer" in point6 ? [point6.layer] : point6.layers;
21255
+ }
21256
+ function connectionPointsMatch(first, second) {
21257
+ return pointsMatch(first, second) && getPointLayers2(first).join("\0") === getPointLayers2(second).join("\0") && first.pointId === second.pointId && first.pcb_port_id === second.pcb_port_id;
21258
+ }
21259
+ function pointIsOnBoundary(point6, boundary) {
21260
+ const inside = point6.x >= boundary.minX - EPSILON3 && point6.x <= boundary.maxX + EPSILON3 && point6.y >= boundary.minY - EPSILON3 && point6.y <= boundary.maxY + EPSILON3;
21261
+ const onEdge = Math.abs(point6.x - boundary.minX) <= EPSILON3 || Math.abs(point6.x - boundary.maxX) <= EPSILON3 || Math.abs(point6.y - boundary.minY) <= EPSILON3 || Math.abs(point6.y - boundary.maxY) <= EPSILON3;
21262
+ return inside && onEdge;
21263
+ }
21264
+ function pointIsInsideBounds2(point6, bounds) {
21265
+ return point6.x >= bounds.minX - EPSILON3 && point6.x <= bounds.maxX + EPSILON3 && point6.y >= bounds.minY - EPSILON3 && point6.y <= bounds.maxY + EPSILON3;
21266
+ }
21267
+ function addIssue(issues, code, message, plan, otherConnectionName) {
21268
+ issues.push({
21269
+ code,
21270
+ message,
21271
+ ...plan ? {
21272
+ connectionName: plan.connectionName,
21273
+ busId: plan.busId
21274
+ } : {},
21275
+ ...otherConnectionName ? { otherConnectionName } : {}
21276
+ });
21277
+ }
21278
+ function extractTraceSegments(params) {
21279
+ const { trace, plan, issues } = params;
21280
+ const segments = [];
21281
+ let previousWire;
21282
+ let pendingVia;
21283
+ for (const routePoint of trace.route) {
21284
+ if (routePoint.route_type === "via") {
21285
+ if (!previousWire || !pointsMatch(previousWire, routePoint) || previousWire.layer !== routePoint.from_layer) {
21286
+ addIssue(
21287
+ issues,
21288
+ "disconnected-trace",
21289
+ `Trace ${trace.pcb_trace_id} reaches a via without a matching ${routePoint.from_layer} wire endpoint`,
21290
+ plan
21291
+ );
21292
+ }
21293
+ pendingVia = routePoint;
21294
+ continue;
21295
+ }
21296
+ if (routePoint.route_type !== "wire") {
21297
+ addIssue(
21298
+ issues,
21299
+ "unsupported-route-point",
21300
+ `Trace ${trace.pcb_trace_id} contains unsupported ${routePoint.route_type} geometry`,
21301
+ plan
21302
+ );
21303
+ continue;
21304
+ }
21305
+ if (pendingVia) {
21306
+ if (!pointsMatch(routePoint, pendingVia) || routePoint.layer !== pendingVia.to_layer) {
21307
+ addIssue(
21308
+ issues,
21309
+ "disconnected-trace",
21310
+ `Trace ${trace.pcb_trace_id} does not continue from its via on ${pendingVia.to_layer}`,
21311
+ plan
21312
+ );
21313
+ }
21314
+ previousWire = routePoint;
21315
+ pendingVia = void 0;
21316
+ continue;
21317
+ }
21318
+ if (previousWire) {
21319
+ if (previousWire.layer !== routePoint.layer) {
21320
+ addIssue(
21321
+ issues,
21322
+ "disconnected-trace",
21323
+ `Trace ${trace.pcb_trace_id} changes from ${previousWire.layer} to ${routePoint.layer} without a via`,
21324
+ plan
21325
+ );
21326
+ } else if (!pointsMatch(previousWire, routePoint)) {
21327
+ segments.push({
21328
+ start: { x: previousWire.x, y: previousWire.y },
21329
+ end: { x: routePoint.x, y: routePoint.y },
21330
+ width: routePoint.width,
21331
+ layer: routePoint.layer
21332
+ });
21333
+ }
21334
+ }
21335
+ previousWire = routePoint;
21336
+ }
21337
+ if (pendingVia) {
21338
+ addIssue(
21339
+ issues,
21340
+ "disconnected-trace",
21341
+ `Trace ${trace.pcb_trace_id} ends at a via without a wire on ${pendingVia.to_layer}`,
21342
+ plan
21343
+ );
21344
+ }
21345
+ return segments;
21346
+ }
21347
+ function validatePlanStructure(params) {
21348
+ const { plan, preparedBus, inputSrj, outputSrj, sharedBoundary, issues } = params;
21349
+ const inputConnection = inputSrj.connections[plan.connectionIndex];
21350
+ if (!inputConnection || inputConnection.name !== plan.connectionName) {
21351
+ addIssue(
21352
+ issues,
21353
+ "connection-mismatch",
21354
+ `Plan index ${plan.connectionIndex} does not identify connection ${plan.connectionName}`,
21355
+ plan
21356
+ );
21357
+ return;
21358
+ }
21359
+ const preparedConnection = preparedBus?.connections.find(
21360
+ (connection) => connection.connectionIndex === plan.connectionIndex
21361
+ );
21362
+ if (!preparedConnection || preparedConnection.sourcePointIndex !== plan.sourcePointIndex || !connectionPointsMatch(preparedConnection.sourcePoint, plan.sourcePoint) || preparedConnection.sourceObstacle.obstacleId !== plan.sourceObstacle.obstacleId) {
21363
+ addIssue(
21364
+ issues,
21365
+ "source-mismatch",
21366
+ `Plan ${plan.connectionName} does not start at its prepared component endpoint`,
21367
+ plan
21368
+ );
21369
+ }
21370
+ if (preparedBus?.termination.type !== plan.termination.type) {
21371
+ addIssue(
21372
+ issues,
21373
+ "termination-mismatch",
21374
+ `Plan ${plan.connectionName} does not use its bus termination`,
21375
+ plan
21376
+ );
21377
+ }
21378
+ if (plan.segments.length === 0 || plan.length <= EPSILON3) {
21379
+ addIssue(
21380
+ issues,
21381
+ "not-broken-out",
21382
+ `Plan ${plan.connectionName} has no non-zero escape geometry`,
21383
+ plan
21384
+ );
21385
+ } else {
21386
+ const routableBounds = {
21387
+ minX: Math.min(inputSrj.bounds.minX, sharedBoundary.minX),
21388
+ maxX: Math.max(inputSrj.bounds.maxX, sharedBoundary.maxX),
21389
+ minY: Math.min(inputSrj.bounds.minY, sharedBoundary.minY),
21390
+ maxY: Math.max(inputSrj.bounds.maxY, sharedBoundary.maxY)
21391
+ };
21392
+ if (plan.segments.some(
21393
+ (segment2) => !pointIsInsideBounds2(segment2.start, routableBounds) || !pointIsInsideBounds2(segment2.end, routableBounds)
21394
+ )) {
21395
+ addIssue(
21396
+ issues,
21397
+ "outside-routing-bounds",
21398
+ `Plan ${plan.connectionName} leaves the routable SRJ/shared-boundary area`,
21399
+ plan
21400
+ );
21401
+ }
21402
+ if (!pointsMatch(plan.segments[0].start, plan.sourcePoint)) {
21403
+ addIssue(
21404
+ issues,
21405
+ "disconnected-trace",
21406
+ `Plan ${plan.connectionName} does not start at its source pad`,
21407
+ plan
21408
+ );
21409
+ }
21410
+ if (!pointsMatch(plan.segments.at(-1).end, plan.exitPoint)) {
21411
+ addIssue(
21412
+ issues,
21413
+ "disconnected-trace",
21414
+ `Plan ${plan.connectionName} does not end at its declared exit`,
21415
+ plan
21416
+ );
21417
+ }
21418
+ for (let index = 1; index < plan.segments.length; index++) {
21419
+ const previous = plan.segments[index - 1];
21420
+ const current = plan.segments[index];
21421
+ if (!pointsMatch(previous.end, current.start)) {
21422
+ addIssue(
21423
+ issues,
21424
+ "disconnected-trace",
21425
+ `Plan ${plan.connectionName} has a gap between route segments`,
21426
+ plan
21427
+ );
21428
+ }
21429
+ if (previous.layer !== current.layer && (!plan.via || !pointsMatch(previous.end, plan.via.center) || !plan.via.spanLayers.includes(previous.layer) || !plan.via.spanLayers.includes(current.layer))) {
21430
+ addIssue(
21431
+ issues,
21432
+ "disconnected-trace",
21433
+ `Plan ${plan.connectionName} changes layers without a connecting via`,
21434
+ plan
21435
+ );
21436
+ }
21437
+ }
21438
+ }
21439
+ const traceSegments = extractTraceSegments({
21440
+ trace: plan.trace,
21441
+ plan,
21442
+ issues
21443
+ });
21444
+ if (traceSegments.length !== plan.segments.length || traceSegments.some((segment2, index) => {
21445
+ const declared = plan.segments[index];
21446
+ return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON3 || !pointsMatch(segment2.start, declared.start) || !pointsMatch(segment2.end, declared.end);
21447
+ })) {
21448
+ addIssue(
21449
+ issues,
21450
+ "trace-plan-mismatch",
21451
+ `Trace ${plan.trace.pcb_trace_id} does not encode its declared route segments`,
21452
+ plan
21453
+ );
21454
+ }
21455
+ const firstRoutePoint = plan.trace.route.find(
21456
+ (routePoint) => "x" in routePoint && "y" in routePoint
21457
+ );
21458
+ const lastRoutePoint = [...plan.trace.route].reverse().find(
21459
+ (routePoint) => "x" in routePoint && "y" in routePoint
21460
+ );
21461
+ if (!firstRoutePoint || !lastRoutePoint || !pointsMatch(firstRoutePoint, plan.sourcePoint) || !pointsMatch(lastRoutePoint, plan.exitPoint)) {
21462
+ addIssue(
21463
+ issues,
21464
+ "disconnected-trace",
21465
+ `Trace ${plan.trace.pcb_trace_id} does not span its source and exit`,
21466
+ plan
21467
+ );
21468
+ }
21469
+ const outputConnection = outputSrj.connections.find(
21470
+ (connection) => connection.name === plan.connectionName
21471
+ );
21472
+ if (plan.termination.type === "boundary") {
21473
+ if (!outputConnection) {
21474
+ addIssue(
21475
+ issues,
21476
+ "output-connection-missing",
21477
+ `Boundary connection ${plan.connectionName} was removed from the output`,
21478
+ plan
21479
+ );
21480
+ } else {
21481
+ const outputSource = outputConnection.pointsToConnect[plan.sourcePointIndex];
21482
+ if (!outputSource || !pointsMatch(outputSource, plan.exitPoint) || !("layer" in outputSource) || outputSource.layer !== plan.targetLayer) {
21483
+ addIssue(
21484
+ issues,
21485
+ "output-exit-mismatch",
21486
+ `Output connection ${plan.connectionName} is not attached to its fanout exit`,
21487
+ plan
21488
+ );
21489
+ }
21490
+ for (let index = 0; index < inputConnection.pointsToConnect.length; index++) {
21491
+ if (index === plan.sourcePointIndex) continue;
21492
+ const inputPoint = inputConnection.pointsToConnect[index];
21493
+ const outputPoint = outputConnection.pointsToConnect[index];
21494
+ if (!inputPoint || !outputPoint || !connectionPointsMatch(inputPoint, outputPoint)) {
21495
+ addIssue(
21496
+ issues,
21497
+ "downstream-endpoint-lost",
21498
+ `Output connection ${plan.connectionName} did not retain downstream endpoint ${index}`,
21499
+ plan
21500
+ );
21501
+ }
21502
+ }
21503
+ }
21504
+ } else if (outputConnection) {
21505
+ addIssue(
21506
+ issues,
21507
+ "plane-connection-retained",
21508
+ `Plane-terminated connection ${plan.connectionName} remains in the output`,
21509
+ plan
21510
+ );
21511
+ }
21512
+ }
21513
+ function plansHaveConnectedCopper(first, second) {
21514
+ for (const firstSegment of first.segments) {
21515
+ for (const secondSegment of second.segments) {
21516
+ if (firstSegment.layer === secondSegment.layer && distanceSegmentToSegment(
21517
+ firstSegment.start,
21518
+ firstSegment.end,
21519
+ secondSegment.start,
21520
+ secondSegment.end
21521
+ ) <= (firstSegment.width + secondSegment.width) / 2 + EPSILON3) {
21522
+ return true;
21523
+ }
21524
+ }
21525
+ if (second.via?.spanLayers.includes(firstSegment.layer) && distancePointToSegment(
21526
+ second.via.center,
21527
+ firstSegment.start,
21528
+ firstSegment.end
21529
+ ) <= second.via.diameter / 2 + firstSegment.width / 2 + EPSILON3) {
21530
+ return true;
21531
+ }
21532
+ }
21533
+ if (first.via) {
21534
+ for (const secondSegment of second.segments) {
21535
+ if (first.via.spanLayers.includes(secondSegment.layer) && distancePointToSegment(
21536
+ first.via.center,
21537
+ secondSegment.start,
21538
+ secondSegment.end
21539
+ ) <= first.via.diameter / 2 + secondSegment.width / 2 + EPSILON3) {
21540
+ return true;
21541
+ }
21542
+ }
21543
+ if (second.via && first.via.spanLayers.some(
21544
+ (layer) => second.via.spanLayers.includes(layer)
21545
+ ) && distance9(first.via.center, second.via.center) <= (first.via.diameter + second.via.diameter) / 2 + EPSILON3) {
21546
+ return true;
21547
+ }
21548
+ }
21549
+ return false;
21550
+ }
21551
+ function validateBreakoutConnectivity(params) {
21552
+ const { plans, inputSrj, sharedBoundary, issues } = params;
21553
+ const connectedPlans = /* @__PURE__ */ new Set();
21554
+ const neighboringPlans = /* @__PURE__ */ new Map();
21555
+ for (const plan of plans) neighboringPlans.set(plan, []);
21556
+ for (let firstIndex = 0; firstIndex < plans.length; firstIndex++) {
21557
+ const first = plans[firstIndex];
21558
+ if (first.termination.type === "plane" ? Boolean(first.via) : first.segments.some(
21559
+ (segment2) => pointIsOnBoundary(segment2.start, sharedBoundary) || pointIsOnBoundary(segment2.end, sharedBoundary)
21560
+ )) {
21561
+ connectedPlans.add(first);
21562
+ }
21563
+ for (let secondIndex = firstIndex + 1; secondIndex < plans.length; secondIndex++) {
21564
+ const second = plans[secondIndex];
21565
+ if (!connectionsShareElectricalNet(
21566
+ inputSrj,
21567
+ first.connectionName,
21568
+ second.connectionName
21569
+ ) || !plansHaveConnectedCopper(first, second)) {
21570
+ continue;
21571
+ }
21572
+ neighboringPlans.get(first).push(second);
21573
+ neighboringPlans.get(second).push(first);
21574
+ }
21575
+ }
21576
+ const queue = [...connectedPlans];
21577
+ while (queue.length > 0) {
21578
+ const plan = queue.shift();
21579
+ for (const neighbor of neighboringPlans.get(plan) ?? []) {
21580
+ if (connectedPlans.has(neighbor)) continue;
21581
+ connectedPlans.add(neighbor);
21582
+ queue.push(neighbor);
21583
+ }
21584
+ }
21585
+ for (const plan of plans) {
21586
+ if (connectedPlans.has(plan)) continue;
21587
+ addIssue(
21588
+ issues,
21589
+ "not-broken-out",
21590
+ plan.termination.type === "boundary" ? `Connection ${plan.connectionName} has no continuous same-net copper path to the shared boundary` : `Plane connection ${plan.connectionName} has no terminating via`,
21591
+ plan
21592
+ );
21593
+ }
21594
+ return connectedPlans;
21595
+ }
21596
+ function validateClearances(params) {
21597
+ const { plans, inputSrj, clearance, issues } = params;
21598
+ for (const plan of plans) {
21599
+ for (let segmentIndex = 0; segmentIndex < plan.segments.length; segmentIndex++) {
21600
+ const segment2 = plan.segments[segmentIndex];
21601
+ for (const obstacle of inputSrj.obstacles) {
21602
+ if (!obstacle.layers.includes(segment2.layer)) continue;
21603
+ if (obstacleSharesElectricalNet(inputSrj, obstacle, plan.connectionName)) {
21604
+ continue;
21605
+ }
21606
+ if (segmentIndex === 0 && obstacle.obstacleId === plan.sourceObstacle.obstacleId && segment2.layer === plan.sourceLayer) {
21607
+ continue;
21608
+ }
21609
+ const actual = distanceSegmentToObstacle(segment2, obstacle);
21610
+ const required = segment2.width / 2 + clearance;
21611
+ if (actual < required - 1e-9) {
21612
+ addIssue(
21613
+ issues,
21614
+ "obstacle-clearance",
21615
+ `Trace ${plan.connectionName} on ${segment2.layer} is ${actual.toFixed(4)}mm from different-net obstacle ${obstacle.obstacleId}; ${required.toFixed(4)}mm is required`,
21616
+ plan
21617
+ );
21618
+ }
21619
+ }
21620
+ }
21621
+ if (plan.via) {
21622
+ for (const obstacle of inputSrj.obstacles) {
21623
+ if (!obstacle.layers.some(
21624
+ (layer) => plan.via.spanLayers.includes(layer)
21625
+ ) || obstacleSharesElectricalNet(inputSrj, obstacle, plan.connectionName)) {
21626
+ continue;
21627
+ }
21628
+ const actual = distancePointToObstacle(plan.via.center, obstacle);
21629
+ const required = plan.via.diameter / 2 + clearance;
21630
+ if (actual < required - 1e-9) {
21631
+ addIssue(
21632
+ issues,
21633
+ "via-obstacle-clearance",
21634
+ `Via ${plan.connectionName} is ${actual.toFixed(4)}mm from different-net obstacle ${obstacle.obstacleId} on its layer span; ${required.toFixed(4)}mm is required`,
21635
+ plan
21636
+ );
21637
+ }
21638
+ }
21639
+ }
21640
+ }
21641
+ for (let firstIndex = 0; firstIndex < plans.length; firstIndex++) {
21642
+ const first = plans[firstIndex];
21643
+ for (let secondIndex = firstIndex + 1; secondIndex < plans.length; secondIndex++) {
21644
+ const second = plans[secondIndex];
21645
+ if (connectionsShareElectricalNet(
21646
+ inputSrj,
21647
+ first.connectionName,
21648
+ second.connectionName
21649
+ )) {
21650
+ continue;
21651
+ }
21652
+ for (const firstSegment of first.segments) {
21653
+ for (const secondSegment of second.segments) {
21654
+ if (!segmentsAreClear(firstSegment, secondSegment, clearance)) {
21655
+ addIssue(
21656
+ issues,
21657
+ "different-net-trace-clearance",
21658
+ `Different-net traces ${first.connectionName} and ${second.connectionName} intersect or violate clearance on ${firstSegment.layer}`,
21659
+ first,
21660
+ second.connectionName
21661
+ );
21662
+ }
21663
+ }
21664
+ if (second.via?.spanLayers.includes(firstSegment.layer) && distancePointToSegment(
21665
+ second.via.center,
21666
+ firstSegment.start,
21667
+ firstSegment.end
21668
+ ) < second.via.diameter / 2 + firstSegment.width / 2 + clearance - 1e-9) {
21669
+ addIssue(
21670
+ issues,
21671
+ "different-net-trace-via-clearance",
21672
+ `Trace ${first.connectionName} violates via clearance to ${second.connectionName} on ${firstSegment.layer}`,
21673
+ first,
21674
+ second.connectionName
21675
+ );
21676
+ }
21677
+ }
21678
+ if (first.via) {
21679
+ for (const secondSegment of second.segments) {
21680
+ if (first.via.spanLayers.includes(secondSegment.layer) && distancePointToSegment(
21681
+ first.via.center,
21682
+ secondSegment.start,
21683
+ secondSegment.end
21684
+ ) < first.via.diameter / 2 + secondSegment.width / 2 + clearance - 1e-9) {
21685
+ addIssue(
21686
+ issues,
21687
+ "different-net-trace-via-clearance",
21688
+ `Via ${first.connectionName} violates trace clearance to ${second.connectionName} on ${secondSegment.layer}`,
21689
+ first,
21690
+ second.connectionName
21691
+ );
21692
+ }
21693
+ }
21694
+ if (second.via && first.via.spanLayers.some(
21695
+ (layer) => second.via.spanLayers.includes(layer)
21696
+ ) && distance9(first.via.center, second.via.center) < (first.via.diameter + second.via.diameter) / 2 + clearance - 1e-9) {
21697
+ addIssue(
21698
+ issues,
21699
+ "different-net-via-clearance",
21700
+ `Vias ${first.connectionName} and ${second.connectionName} violate clearance on an overlapping layer span`,
21701
+ first,
21702
+ second.connectionName
21703
+ );
21704
+ }
21705
+ }
21706
+ }
21707
+ }
21708
+ }
21709
+ function validateFanoutSolution(params) {
21710
+ const {
21711
+ inputSrj,
21712
+ outputSrj,
21713
+ plans,
21714
+ preparedBuses,
21715
+ sharedBoundary,
21716
+ clearance
21717
+ } = params;
21718
+ const issues = [];
21719
+ const plansByConnection = /* @__PURE__ */ new Map();
21720
+ const preparedBusById = new Map(preparedBuses.map((bus) => [bus.busId, bus]));
21721
+ for (const plan of plans) {
21722
+ const connectionPlans = plansByConnection.get(plan.connectionName) ?? [];
21723
+ connectionPlans.push(plan);
21724
+ plansByConnection.set(plan.connectionName, connectionPlans);
21725
+ }
21726
+ for (const connection of inputSrj.connections) {
21727
+ const connectionPlans = plansByConnection.get(connection.name) ?? [];
21728
+ if (connectionPlans.length === 0) {
21729
+ addIssue(
21730
+ issues,
21731
+ "missing-plan",
21732
+ `Connection ${connection.name} has no fanout plan`
21733
+ );
21734
+ } else if (connectionPlans.length > 1) {
21735
+ addIssue(
21736
+ issues,
21737
+ "duplicate-plan",
21738
+ `Connection ${connection.name} has ${connectionPlans.length} fanout plans`,
21739
+ connectionPlans[0]
21740
+ );
21741
+ }
21742
+ }
21743
+ for (const plan of plans) {
21744
+ if (!inputSrj.connections.some(
21745
+ (connection) => connection.name === plan.connectionName
21746
+ )) {
21747
+ addIssue(
21748
+ issues,
21749
+ "unknown-plan",
21750
+ `Plan ${plan.connectionName} is not an input connection`,
21751
+ plan
21752
+ );
21753
+ continue;
21754
+ }
21755
+ validatePlanStructure({
21756
+ plan,
21757
+ preparedBus: preparedBusById.get(plan.busId),
21758
+ inputSrj,
21759
+ outputSrj,
21760
+ sharedBoundary,
21761
+ issues
21762
+ });
21763
+ }
21764
+ const connectedPlans = validateBreakoutConnectivity({
21765
+ plans,
21766
+ inputSrj,
21767
+ sharedBoundary,
21768
+ issues
21769
+ });
21770
+ validateClearances({ plans, inputSrj, clearance, issues });
21771
+ return {
21772
+ valid: issues.length === 0,
21773
+ checkedConnectionCount: inputSrj.connections.length,
21774
+ brokenOutConnectionCount: new Set(
21775
+ [...connectedPlans].map((plan) => plan.connectionName)
21776
+ ).size,
21777
+ issues
21778
+ };
21779
+ }
21780
+
20981
21781
  // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
20982
21782
  function resolvePositiveNumber(label, value) {
20983
21783
  if (!Number.isFinite(value) || value <= 0) {
@@ -21033,6 +21833,7 @@ function resolveConfig(srj, options) {
21033
21833
  viaHoleDiameter,
21034
21834
  clearance,
21035
21835
  compactBusTracks: options.compactBusTracks ?? false,
21836
+ allowSameNetMerges: options.allowSameNetMerges ?? false,
21036
21837
  singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
21037
21838
  singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
21038
21839
  borderDistribution,
@@ -21093,44 +21894,8 @@ function getBusDepthInRows(bus) {
21093
21894
  Math.abs(averageSource - outwardCoordinate) / directionalPitch
21094
21895
  );
21095
21896
  }
21096
- function sourceLayerEscapeIsBlocked(params) {
21097
- const { bus, srj, traceWidth, clearance } = params;
21098
- for (const connection of bus.connections) {
21099
- const source = {
21100
- x: connection.sourcePoint.x,
21101
- y: connection.sourcePoint.y
21102
- };
21103
- const boundaryPoint = (() => {
21104
- switch (bus.direction) {
21105
- case "left":
21106
- return { x: bus.sharedBoundary.minX, y: source.y };
21107
- case "right":
21108
- return { x: bus.sharedBoundary.maxX, y: source.y };
21109
- case "up":
21110
- return { x: source.x, y: bus.sharedBoundary.maxY };
21111
- case "down":
21112
- return { x: source.x, y: bus.sharedBoundary.minY };
21113
- }
21114
- })();
21115
- const directEscapeSegment = {
21116
- start: source,
21117
- end: boundaryPoint,
21118
- width: traceWidth,
21119
- layer: connection.sourceLayer
21120
- };
21121
- for (const obstacle of srj.obstacles) {
21122
- if (obstacle === connection.sourceObstacle || !obstacle.layers.includes(connection.sourceLayer)) {
21123
- continue;
21124
- }
21125
- if (distanceSegmentToObstacle(directEscapeSegment, obstacle) < traceWidth / 2 + clearance - 1e-9) {
21126
- return true;
21127
- }
21128
- }
21129
- }
21130
- return false;
21131
- }
21132
21897
  function createPreferredLayerAssignment(params) {
21133
- const { buses, escapeLayers, srj, traceWidth, clearance } = params;
21898
+ const { buses, escapeLayers, escapeLayersByBusId } = params;
21134
21899
  const assignment = {};
21135
21900
  const directionsByComponent = /* @__PURE__ */ new Map();
21136
21901
  let nextViaLayerIndex = 0;
@@ -21148,13 +21913,11 @@ function createPreferredLayerAssignment(params) {
21148
21913
  assignment[bus.busId] = bus.termination.layer;
21149
21914
  continue;
21150
21915
  }
21151
- const viaLayers = escapeLayers.filter((layer) => layer !== sourceLayer);
21152
- if (escapeLayers.includes(sourceLayer) && busIsOnOutwardComponentEdge2(bus) && !sourceLayerEscapeIsBlocked({
21153
- bus,
21154
- srj,
21155
- traceWidth,
21156
- clearance
21157
- })) {
21916
+ const routableEscapeLayers = escapeLayersByBusId[bus.busId] ?? escapeLayers;
21917
+ const viaLayers = routableEscapeLayers.filter(
21918
+ (layer) => layer !== sourceLayer
21919
+ );
21920
+ if (routableEscapeLayers.includes(sourceLayer) && busIsOnOutwardComponentEdge2(bus)) {
21158
21921
  assignment[bus.busId] = sourceLayer;
21159
21922
  } else if (viaLayers.length > 0) {
21160
21923
  const componentDirections = directionsByComponent.get(bus.componentId);
@@ -21182,6 +21945,26 @@ function prioritizeLayerAssignment(params) {
21182
21945
  )
21183
21946
  ].slice(0, maxAssignments);
21184
21947
  }
21948
+ function getCandidateEscapeLayersForBus(params) {
21949
+ const { bus, srj, config, staticClearanceCache } = params;
21950
+ const individuallyRoutableLayers = config.escapeLayers.filter(
21951
+ (targetLayer) => routeBus({
21952
+ srj,
21953
+ bus,
21954
+ targetLayer,
21955
+ acceptedPlans: [],
21956
+ layerNames: config.layerNames,
21957
+ traceWidth: config.traceWidth,
21958
+ viaDiameter: config.viaDiameter,
21959
+ viaHoleDiameter: config.viaHoleDiameter,
21960
+ clearance: config.clearance,
21961
+ compactBusTracks: config.compactBusTracks,
21962
+ allowSameNetMerges: config.allowSameNetMerges,
21963
+ staticClearanceCache
21964
+ }) !== null
21965
+ );
21966
+ return individuallyRoutableLayers.length > 0 ? individuallyRoutableLayers : config.escapeLayers;
21967
+ }
21185
21968
  var FanoutSolver = class extends BaseSolver {
21186
21969
  constructor(inputSrj, options = {}) {
21187
21970
  super();
@@ -21211,9 +21994,27 @@ var FanoutSolver = class extends BaseSolver {
21211
21994
  (bus) => bus.termination.type === "plane" ? [[bus.busId, bus.termination.layer]] : []
21212
21995
  )
21213
21996
  );
21997
+ const escapeLayersByBusId = Object.fromEntries(
21998
+ this.preparedBuses.flatMap((bus) => {
21999
+ if (bus.termination.type === "plane") return [];
22000
+ return [
22001
+ [
22002
+ bus.busId,
22003
+ getCandidateEscapeLayersForBus({
22004
+ bus,
22005
+ srj: inputSrj,
22006
+ config: this.config,
22007
+ staticClearanceCache: this.routeStaticClearanceCache
22008
+ })
22009
+ ]
22010
+ ];
22011
+ })
22012
+ );
22013
+ this.escapeLayersByBusId = escapeLayersByBusId;
21214
22014
  const generatedAssignments = generateLayerAssignments({
21215
22015
  busIds: boundaryBusIds,
21216
22016
  layers: this.config.escapeLayers,
22017
+ layersByBusId: escapeLayersByBusId,
21217
22018
  maxAssignments: this.config.maxLayerCombinations
21218
22019
  }).map((assignment) => ({
21219
22020
  ...assignment,
@@ -21223,14 +22024,12 @@ var FanoutSolver = class extends BaseSolver {
21223
22024
  preferredAssignment: createPreferredLayerAssignment({
21224
22025
  buses: this.preparedBuses,
21225
22026
  escapeLayers: this.config.escapeLayers,
21226
- srj: inputSrj,
21227
- traceWidth: this.config.traceWidth,
21228
- clearance: this.config.clearance
22027
+ escapeLayersByBusId
21229
22028
  }),
21230
22029
  generatedAssignments,
21231
22030
  maxAssignments: this.config.maxLayerCombinations
21232
22031
  });
21233
- this.MAX_ITERATIONS = this.layerAssignments.length + 2;
22032
+ this.MAX_ITERATIONS = this.config.maxLayerCombinations + 2;
21234
22033
  }
21235
22034
  inputSrj;
21236
22035
  options;
@@ -21238,14 +22037,48 @@ var FanoutSolver = class extends BaseSolver {
21238
22037
  attempts = [];
21239
22038
  layerAssignments;
21240
22039
  config;
22040
+ escapeLayersByBusId;
22041
+ evaluatedAssignmentKeys = /* @__PURE__ */ new Set();
22042
+ queuedAssignmentKeys = /* @__PURE__ */ new Set();
22043
+ assignmentRepairDepthByKey = /* @__PURE__ */ new Map();
22044
+ pendingRepairAssignments = [];
22045
+ routeStaticClearanceCache = /* @__PURE__ */ new Map();
22046
+ routingPrefixCache = /* @__PURE__ */ new Map();
22047
+ groupedBeamEvaluated = false;
21241
22048
  nextAssignmentIndex = 0;
22049
+ nextGeneratedAssignmentIndex = 0;
21242
22050
  bestAttempt = null;
21243
22051
  getSolverName() {
21244
22052
  return "FanoutSolver";
21245
22053
  }
21246
- evaluateAssignment(assignmentIndex, busLayerAssignments) {
21247
- const plans = [];
21248
- const failedBusIds = [];
22054
+ getValidationBoundary() {
22055
+ if (this.options.sharedBoundary) return this.options.sharedBoundary;
22056
+ const firstBoundary = this.preparedBuses[0]?.sharedBoundary;
22057
+ if (!firstBoundary) return this.inputSrj.bounds;
22058
+ return this.preparedBuses.slice(1).reduce(
22059
+ (boundary, bus) => ({
22060
+ minX: Math.min(boundary.minX, bus.sharedBoundary.minX),
22061
+ maxX: Math.max(boundary.maxX, bus.sharedBoundary.maxX),
22062
+ minY: Math.min(boundary.minY, bus.sharedBoundary.minY),
22063
+ maxY: Math.max(boundary.maxY, bus.sharedBoundary.maxY)
22064
+ }),
22065
+ { ...firstBoundary }
22066
+ );
22067
+ }
22068
+ validateCompletePlans(plans, outputSrj) {
22069
+ return validateFanoutSolution({
22070
+ inputSrj: this.inputSrj,
22071
+ outputSrj,
22072
+ plans,
22073
+ preparedBuses: this.preparedBuses,
22074
+ sharedBoundary: this.getValidationBoundary(),
22075
+ clearance: this.config.clearance
22076
+ });
22077
+ }
22078
+ evaluateAssignmentWithStrategy(assignmentIndex, busLayerAssignments, routingStrategy) {
22079
+ let plans = [];
22080
+ let failedBusIds = [];
22081
+ let blockingBusCounts = /* @__PURE__ */ new Map();
21249
22082
  const isSingleLayerFanout = this.config.escapeLayers.length === 1;
21250
22083
  if (isSingleLayerFanout && this.config.singleLayerPushAndShove) {
21251
22084
  const singleLayerParams = {
@@ -21268,8 +22101,11 @@ var FanoutSolver = class extends BaseSolver {
21268
22101
  }
21269
22102
  }
21270
22103
  const busesInRoutingOrder = [...this.preparedBuses].sort(
21271
- (a, b) => Number(a.termination.type === "plane") - Number(b.termination.type === "plane") || b.componentObstacles.length - a.componentObstacles.length || (isSingleLayerFanout ? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a) : b.connections.length - a.connections.length || getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b))
22104
+ (a, b) => Number(a.termination.type === "plane") - Number(b.termination.type === "plane") || (routingStrategy === "group-by-layer" ? (busLayerAssignments[a.busId] ?? "").localeCompare(
22105
+ busLayerAssignments[b.busId] ?? ""
22106
+ ) : 0) || b.componentObstacles.length - a.componentObstacles.length || (isSingleLayerFanout ? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a) : b.connections.length - a.connections.length || (routingStrategy === "deep-first" ? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a) : getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b)))
21272
22107
  );
22108
+ let routingPrefixKey = `${routingStrategy}|`;
21273
22109
  for (const bus of isSingleLayerFanout && this.config.singleLayerPushAndShove ? [] : busesInRoutingOrder) {
21274
22110
  const targetLayer = busLayerAssignments[bus.busId];
21275
22111
  if (!targetLayer) {
@@ -21277,6 +22113,15 @@ var FanoutSolver = class extends BaseSolver {
21277
22113
  `FanoutSolver: assignment ${assignmentIndex} has no layer for bus "${bus.busId}"`
21278
22114
  );
21279
22115
  }
22116
+ routingPrefixKey += `${bus.busId.length}:${bus.busId};${targetLayer.length}:${targetLayer};`;
22117
+ const cachedPrefix = this.routingPrefixCache.get(routingPrefixKey);
22118
+ if (cachedPrefix) {
22119
+ plans = [...cachedPrefix.plans];
22120
+ failedBusIds = [...cachedPrefix.failedBusIds];
22121
+ blockingBusCounts = new Map(cachedPrefix.blockingBusCounts);
22122
+ continue;
22123
+ }
22124
+ const currentBusBlockingCounts = /* @__PURE__ */ new Map();
21280
22125
  const busPlans = routeBus({
21281
22126
  srj: this.inputSrj,
21282
22127
  bus,
@@ -21287,13 +22132,45 @@ var FanoutSolver = class extends BaseSolver {
21287
22132
  viaDiameter: this.config.viaDiameter,
21288
22133
  viaHoleDiameter: this.config.viaHoleDiameter,
21289
22134
  clearance: this.config.clearance,
21290
- compactBusTracks: this.config.compactBusTracks
22135
+ compactBusTracks: this.config.compactBusTracks,
22136
+ allowSameNetMerges: this.config.allowSameNetMerges,
22137
+ staticClearanceCache: this.routeStaticClearanceCache,
22138
+ blockingBusCounts: currentBusBlockingCounts
21291
22139
  });
21292
22140
  if (!busPlans) {
21293
22141
  failedBusIds.push(bus.busId);
21294
- continue;
22142
+ for (const [blockingBusId, count] of currentBusBlockingCounts) {
22143
+ blockingBusCounts.set(
22144
+ blockingBusId,
22145
+ (blockingBusCounts.get(blockingBusId) ?? 0) + count
22146
+ );
22147
+ }
22148
+ } else {
22149
+ plans.push(...busPlans);
21295
22150
  }
21296
- plans.push(...busPlans);
22151
+ this.routingPrefixCache.set(routingPrefixKey, {
22152
+ plans: [...plans],
22153
+ failedBusIds: [...failedBusIds],
22154
+ blockingBusCounts: new Map(blockingBusCounts)
22155
+ });
22156
+ }
22157
+ let validationIssues;
22158
+ let outputSrj = buildOutputSimpleRouteJson({
22159
+ inputSrj: this.inputSrj,
22160
+ plans,
22161
+ layerNames: this.config.layerNames
22162
+ });
22163
+ const validation = plans.length === this.inputSrj.connections.length ? this.validateCompletePlans(plans, outputSrj) : null;
22164
+ if (validation && !validation.valid) {
22165
+ validationIssues = validation.issues;
22166
+ plans = [];
22167
+ failedBusIds = this.preparedBuses.map((bus) => bus.busId);
22168
+ blockingBusCounts.clear();
22169
+ outputSrj = buildOutputSimpleRouteJson({
22170
+ inputSrj: this.inputSrj,
22171
+ plans,
22172
+ layerNames: this.config.layerNames
22173
+ });
21297
22174
  }
21298
22175
  const routedBusCount = this.preparedBuses.length - failedBusIds.length;
21299
22176
  const routeLength = plans.reduce((total, plan) => total + plan.length, 0);
@@ -21305,20 +22182,271 @@ var FanoutSolver = class extends BaseSolver {
21305
22182
  routedBusCount,
21306
22183
  routedConnectionCount: plans.length,
21307
22184
  failedBusIds,
21308
- score
22185
+ score,
22186
+ ...validationIssues ? { validationIssues } : {}
21309
22187
  };
21310
22188
  return {
21311
22189
  summary,
21312
22190
  plans,
21313
- outputSrj: buildOutputSimpleRouteJson({
21314
- inputSrj: this.inputSrj,
21315
- plans,
21316
- layerNames: this.config.layerNames
21317
- })
22191
+ blockingBusIds: [...blockingBusCounts.entries()].toSorted(([, firstCount], [, secondCount]) => secondCount - firstCount).map(([busId]) => busId),
22192
+ outputSrj
21318
22193
  };
21319
22194
  }
22195
+ evaluateAssignment(assignmentIndex, busLayerAssignments) {
22196
+ let bestAttempt = this.evaluateAssignmentWithStrategy(
22197
+ assignmentIndex,
22198
+ busLayerAssignments,
22199
+ "default"
22200
+ );
22201
+ if (bestAttempt.summary.routedConnectionCount === this.inputSrj.connections.length) {
22202
+ return bestAttempt;
22203
+ }
22204
+ for (const routingStrategy of ["group-by-layer", "deep-first"]) {
22205
+ const attempt = this.evaluateAssignmentWithStrategy(
22206
+ assignmentIndex,
22207
+ busLayerAssignments,
22208
+ routingStrategy
22209
+ );
22210
+ if (attempt.summary.score < bestAttempt.summary.score) {
22211
+ bestAttempt = attempt;
22212
+ }
22213
+ if (bestAttempt.summary.routedConnectionCount === this.inputSrj.connections.length) {
22214
+ return bestAttempt;
22215
+ }
22216
+ }
22217
+ return bestAttempt;
22218
+ }
22219
+ /**
22220
+ * Search layer assignments and track alternatives together. The regular
22221
+ * assignment loop commits to one route per bus before the next bus is
22222
+ * considered, so a locally-valid track can still starve a later bus. A
22223
+ * bounded beam keeps several grouped-layer route prefixes alive. It also
22224
+ * evaluates multi-connection buses atomically, so one promising route for a
22225
+ * power or signal lane cannot starve a later bus before the solver explores
22226
+ * an alternate layer/track combination.
22227
+ */
22228
+ evaluateGroupedBeam(assignmentIndex, groupByDirection = false) {
22229
+ if (this.config.escapeLayers.length < 2) return null;
22230
+ if (this.preparedBuses.length > 56) return null;
22231
+ const totalConnections = this.inputSrj.connections.length;
22232
+ if (totalConnections > 64) return null;
22233
+ if (new Set(this.preparedBuses.map((bus) => bus.componentId)).size !== 1) {
22234
+ return null;
22235
+ }
22236
+ const busesInSearchOrder = [...this.preparedBuses].sort((a, b) => {
22237
+ const aLayerCount = a.termination.type === "plane" ? 1 : this.escapeLayersByBusId[a.busId]?.length ?? this.config.escapeLayers.length;
22238
+ const bLayerCount = b.termination.type === "plane" ? 1 : this.escapeLayersByBusId[b.busId]?.length ?? this.config.escapeLayers.length;
22239
+ return Number(a.termination.type === "plane") - Number(b.termination.type === "plane") || (groupByDirection ? a.direction.localeCompare(b.direction) : 0) || aLayerCount - bLayerCount || b.componentObstacles.length - a.componentObstacles.length || b.connections.length - a.connections.length || getBusDepthInRows(b) - getBusDepthInRows(a) || a.busId.localeCompare(b.busId);
22240
+ });
22241
+ const isSmallProblem = totalConnections <= 24;
22242
+ const hasMultiConnectionBus = this.preparedBuses.some(
22243
+ (bus) => bus.connections.length > 1
22244
+ );
22245
+ const beamWidth = isSmallProblem ? 48 : totalConnections <= 32 ? 24 : 12;
22246
+ const alternativesPerLayer = isSmallProblem && !hasMultiConnectionBus ? 4 : 1;
22247
+ let states = [{ assignment: {}, plans: [] }];
22248
+ const getStateScore = (state) => {
22249
+ const routeLength = state.plans.reduce(
22250
+ (total, plan) => total + plan.length,
22251
+ 0
22252
+ );
22253
+ const viaCount = state.plans.filter((plan) => plan.via).length;
22254
+ return routeLength + viaCount * 0.1 + assignmentLoadPenalty(state.assignment) * 0.01;
22255
+ };
22256
+ for (const bus of busesInSearchOrder) {
22257
+ const nextStates = [];
22258
+ for (const state of states) {
22259
+ const candidateLayers = bus.termination.type === "plane" ? [bus.termination.layer] : this.escapeLayersByBusId[bus.busId] ?? this.config.escapeLayers;
22260
+ const layerLoads = /* @__PURE__ */ new Map();
22261
+ for (const layer of Object.values(state.assignment)) {
22262
+ layerLoads.set(layer, (layerLoads.get(layer) ?? 0) + 1);
22263
+ }
22264
+ const sourceLayer = bus.connections[0]?.sourceLayer;
22265
+ const orderedLayers = candidateLayers.toSorted(
22266
+ (first, second) => (layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) || Number(first === sourceLayer) - Number(second === sourceLayer) || first.localeCompare(second)
22267
+ );
22268
+ for (const targetLayer of orderedLayers) {
22269
+ const busAlternatives = routeBusAlternatives(
22270
+ {
22271
+ srj: this.inputSrj,
22272
+ bus,
22273
+ targetLayer,
22274
+ acceptedPlans: state.plans,
22275
+ layerNames: this.config.layerNames,
22276
+ traceWidth: this.config.traceWidth,
22277
+ viaDiameter: this.config.viaDiameter,
22278
+ viaHoleDiameter: this.config.viaHoleDiameter,
22279
+ clearance: this.config.clearance,
22280
+ compactBusTracks: this.config.compactBusTracks,
22281
+ allowSameNetMerges: this.config.allowSameNetMerges,
22282
+ staticClearanceCache: this.routeStaticClearanceCache
22283
+ },
22284
+ alternativesPerLayer
22285
+ );
22286
+ for (const busPlans of busAlternatives) {
22287
+ nextStates.push({
22288
+ assignment: {
22289
+ ...state.assignment,
22290
+ [bus.busId]: targetLayer
22291
+ },
22292
+ plans: [...state.plans, ...busPlans]
22293
+ });
22294
+ }
22295
+ }
22296
+ }
22297
+ if (nextStates.length === 0) return null;
22298
+ nextStates.sort((first, second) => {
22299
+ const scoreDifference = getStateScore(first) - getStateScore(second);
22300
+ if (Math.abs(scoreDifference) > 1e-9) return scoreDifference;
22301
+ return JSON.stringify(first.assignment).localeCompare(
22302
+ JSON.stringify(second.assignment)
22303
+ );
22304
+ });
22305
+ const statesByAssignment = /* @__PURE__ */ new Map();
22306
+ states = [];
22307
+ for (const state of nextStates) {
22308
+ const key = JSON.stringify(state.assignment);
22309
+ const sameAssignmentCount = statesByAssignment.get(key) ?? 0;
22310
+ if (sameAssignmentCount >= 2) continue;
22311
+ statesByAssignment.set(key, sameAssignmentCount + 1);
22312
+ states.push(state);
22313
+ if (states.length >= beamWidth) break;
22314
+ }
22315
+ }
22316
+ const bestState = states[0];
22317
+ if (!bestState) return null;
22318
+ const outputSrj = buildOutputSimpleRouteJson({
22319
+ inputSrj: this.inputSrj,
22320
+ plans: bestState.plans,
22321
+ layerNames: this.config.layerNames
22322
+ });
22323
+ if (bestState.plans.length === this.inputSrj.connections.length && !this.validateCompletePlans(bestState.plans, outputSrj).valid) {
22324
+ return null;
22325
+ }
22326
+ const score = bestState.plans.length === this.inputSrj.connections.length ? bestState.plans.reduce((total, plan) => total + plan.length, 0) + bestState.plans.filter((plan) => plan.via).length * 0.1 + assignmentLoadPenalty(bestState.assignment) * 0.01 : Number.POSITIVE_INFINITY;
22327
+ if (!Number.isFinite(score)) return null;
22328
+ const summary = {
22329
+ assignmentIndex,
22330
+ busLayerAssignments: bestState.assignment,
22331
+ routedBusCount: this.preparedBuses.length,
22332
+ routedConnectionCount: bestState.plans.length,
22333
+ failedBusIds: [],
22334
+ score
22335
+ };
22336
+ return {
22337
+ summary,
22338
+ plans: bestState.plans,
22339
+ blockingBusIds: [],
22340
+ outputSrj
22341
+ };
22342
+ }
22343
+ prioritizeFailedBusRepairs(assignment, failedBusIds, blockingBusIds) {
22344
+ const assignmentKey = JSON.stringify(assignment);
22345
+ const repairDepth = this.assignmentRepairDepthByKey.get(assignmentKey) ?? 0;
22346
+ if (repairDepth >= 2) return;
22347
+ const maximumRepairs = 8;
22348
+ const repairs = [];
22349
+ const repairKeys = /* @__PURE__ */ new Set();
22350
+ const addRepair = (repair) => {
22351
+ const key = JSON.stringify(repair);
22352
+ if (repairKeys.has(key) || this.evaluatedAssignmentKeys.has(key) || this.queuedAssignmentKeys.has(key)) {
22353
+ return;
22354
+ }
22355
+ repairKeys.add(key);
22356
+ this.queuedAssignmentKeys.add(key);
22357
+ this.assignmentRepairDepthByKey.set(key, repairDepth + 1);
22358
+ repairs.push(repair);
22359
+ };
22360
+ const repairBusIds = [];
22361
+ for (let index = 0; index < Math.max(failedBusIds.length, blockingBusIds.length); index++) {
22362
+ const failedBusId = failedBusIds[index];
22363
+ const blockingBusId = blockingBusIds[index];
22364
+ if (failedBusId && !repairBusIds.includes(failedBusId)) {
22365
+ repairBusIds.push(failedBusId);
22366
+ }
22367
+ if (blockingBusId && !repairBusIds.includes(blockingBusId)) {
22368
+ repairBusIds.push(blockingBusId);
22369
+ }
22370
+ }
22371
+ for (const failedBusId of failedBusIds) {
22372
+ const failedLayer = assignment[failedBusId];
22373
+ const failedCandidateLayers = this.escapeLayersByBusId[failedBusId];
22374
+ if (!failedLayer || !failedCandidateLayers) continue;
22375
+ for (const blockingBusId of blockingBusIds.slice(0, 4)) {
22376
+ const blockingLayer = assignment[blockingBusId];
22377
+ const blockingCandidateLayers = this.escapeLayersByBusId[blockingBusId];
22378
+ if (!blockingLayer || !blockingCandidateLayers || !failedCandidateLayers.includes(blockingLayer) || !blockingCandidateLayers.includes(failedLayer)) {
22379
+ continue;
22380
+ }
22381
+ addRepair({
22382
+ ...assignment,
22383
+ [failedBusId]: blockingLayer,
22384
+ [blockingBusId]: failedLayer
22385
+ });
22386
+ if (repairs.length >= maximumRepairs) break;
22387
+ }
22388
+ if (repairs.length >= maximumRepairs) break;
22389
+ }
22390
+ for (const busId of repairBusIds) {
22391
+ const currentLayer = assignment[busId];
22392
+ const candidateLayers = this.escapeLayersByBusId[busId];
22393
+ if (!currentLayer || !candidateLayers) continue;
22394
+ const currentLayerIndex = candidateLayers.indexOf(currentLayer);
22395
+ for (let shift = 1; shift < candidateLayers.length; shift++) {
22396
+ const candidateLayer = candidateLayers[(Math.max(currentLayerIndex, 0) + shift) % candidateLayers.length];
22397
+ if (candidateLayer === currentLayer) continue;
22398
+ addRepair({ ...assignment, [busId]: candidateLayer });
22399
+ if (repairs.length >= maximumRepairs) break;
22400
+ }
22401
+ if (repairs.length >= maximumRepairs) break;
22402
+ }
22403
+ this.pendingRepairAssignments.push(...repairs);
22404
+ }
21320
22405
  _step() {
21321
- const assignment = this.layerAssignments[this.nextAssignmentIndex];
22406
+ if (!this.groupedBeamEvaluated) {
22407
+ this.groupedBeamEvaluated = true;
22408
+ let beamAttempt = this.evaluateGroupedBeam(-1);
22409
+ if (!beamAttempt) {
22410
+ beamAttempt = this.evaluateGroupedBeam(-1, true);
22411
+ }
22412
+ if (beamAttempt) {
22413
+ this.attempts.push(beamAttempt.summary);
22414
+ this.bestAttempt = beamAttempt;
22415
+ this.stats = {
22416
+ assignment: 0,
22417
+ assignmentCount: this.config.maxLayerCombinations,
22418
+ routedBuses: `${beamAttempt.summary.routedBusCount}/${this.preparedBuses.length}`,
22419
+ routedConnections: `${beamAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
22420
+ failedBuses: "none",
22421
+ bestScore: beamAttempt.summary.score
22422
+ };
22423
+ this.solved = true;
22424
+ return;
22425
+ }
22426
+ }
22427
+ let assignment;
22428
+ while (!assignment && this.nextAssignmentIndex < this.config.maxLayerCombinations) {
22429
+ const preferGeneratedAssignment = this.nextAssignmentIndex % 3 === 0;
22430
+ let candidate;
22431
+ let candidateCameFromRepairQueue = false;
22432
+ if (preferGeneratedAssignment) {
22433
+ candidate = this.layerAssignments[this.nextGeneratedAssignmentIndex++];
22434
+ } else {
22435
+ candidate = this.pendingRepairAssignments.pop();
22436
+ candidateCameFromRepairQueue = candidate !== void 0;
22437
+ }
22438
+ if (!candidate) {
22439
+ candidate = preferGeneratedAssignment ? this.pendingRepairAssignments.pop() : this.layerAssignments[this.nextGeneratedAssignmentIndex++];
22440
+ candidateCameFromRepairQueue = preferGeneratedAssignment && candidate !== void 0;
22441
+ }
22442
+ if (!candidate) break;
22443
+ const candidateKey = JSON.stringify(candidate);
22444
+ if (candidateCameFromRepairQueue) {
22445
+ this.queuedAssignmentKeys.delete(candidateKey);
22446
+ }
22447
+ if (this.evaluatedAssignmentKeys.has(candidateKey)) continue;
22448
+ assignment = candidate;
22449
+ }
21322
22450
  if (!assignment) {
21323
22451
  if (this.bestAttempt && this.bestAttempt.summary.routedConnectionCount === this.inputSrj.connections.length) {
21324
22452
  this.solved = true;
@@ -21333,13 +22461,21 @@ var FanoutSolver = class extends BaseSolver {
21333
22461
  assignment
21334
22462
  );
21335
22463
  this.nextAssignmentIndex++;
22464
+ this.evaluatedAssignmentKeys.add(JSON.stringify(assignment));
22465
+ if (!this.bestAttempt || attempt.summary.routedConnectionCount >= this.bestAttempt.summary.routedConnectionCount) {
22466
+ this.prioritizeFailedBusRepairs(
22467
+ assignment,
22468
+ attempt.summary.failedBusIds,
22469
+ attempt.blockingBusIds
22470
+ );
22471
+ }
21336
22472
  this.attempts.push(attempt.summary);
21337
22473
  if (!this.bestAttempt || attempt.summary.score < this.bestAttempt.summary.score) {
21338
22474
  this.bestAttempt = attempt;
21339
22475
  }
21340
22476
  this.stats = {
21341
22477
  assignment: attempt.summary.assignmentIndex + 1,
21342
- assignmentCount: this.layerAssignments.length,
22478
+ assignmentCount: this.config.maxLayerCombinations,
21343
22479
  routedBuses: `${attempt.summary.routedBusCount}/${this.preparedBuses.length}`,
21344
22480
  routedConnections: `${attempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
21345
22481
  failedBuses: attempt.summary.failedBusIds.join(", ") || "none",
@@ -21351,7 +22487,7 @@ var FanoutSolver = class extends BaseSolver {
21351
22487
  }
21352
22488
  computeProgress() {
21353
22489
  if (this.solved || this.failed) return 1;
21354
- return this.nextAssignmentIndex / this.layerAssignments.length;
22490
+ return this.nextAssignmentIndex / this.config.maxLayerCombinations;
21355
22491
  }
21356
22492
  getConstructorParams() {
21357
22493
  return [this.inputSrj, this.options];
@@ -21362,6 +22498,15 @@ var FanoutSolver = class extends BaseSolver {
21362
22498
  "FanoutSolver: getOutput() called before a complete fanout was solved"
21363
22499
  );
21364
22500
  }
22501
+ const validation = this.validateCompletePlans(
22502
+ this.bestAttempt.plans,
22503
+ this.bestAttempt.outputSrj
22504
+ );
22505
+ if (!validation.valid) {
22506
+ throw new Error(
22507
+ `FanoutSolver: completed output failed validation: ${validation.issues[0]?.message ?? "unknown validation error"}`
22508
+ );
22509
+ }
21365
22510
  return {
21366
22511
  simpleRouteJson: this.bestAttempt.outputSrj,
21367
22512
  fanoutTraces: this.bestAttempt.plans.map((plan) => plan.trace),
@@ -21379,7 +22524,8 @@ var FanoutSolver = class extends BaseSolver {
21379
22524
  busDirections: Object.fromEntries(
21380
22525
  this.preparedBuses.map((bus) => [bus.busId, bus.direction])
21381
22526
  ),
21382
- attempts: [...this.attempts]
22527
+ attempts: [...this.attempts],
22528
+ validation
21383
22529
  };
21384
22530
  }
21385
22531
  getOutputSimpleRouteJson() {
@@ -31458,7 +32604,7 @@ function Group_getRoutingPhasePlans(group) {
31458
32604
  var package_default = {
31459
32605
  name: "@tscircuit/core",
31460
32606
  type: "module",
31461
- version: "0.0.1650",
32607
+ version: "0.0.1651",
31462
32608
  types: "dist/index.d.ts",
31463
32609
  main: "dist/index.js",
31464
32610
  module: "dist/index.js",
@@ -31498,7 +32644,7 @@ var package_default = {
31498
32644
  "@tscircuit/common": "^0.0.20",
31499
32645
  "@tscircuit/copper-pour-solver": "0.0.44",
31500
32646
  "@tscircuit/create-fdm-enclosure": "0.0.3",
31501
- "@tscircuit/fanout-solver": "0.0.16",
32647
+ "@tscircuit/fanout-solver": "0.0.20",
31502
32648
  "@tscircuit/footprinter": "^0.0.409",
31503
32649
  "@tscircuit/image-utils": "^0.0.8",
31504
32650
  "@tscircuit/infer-cable-insertion-point": "^0.0.3",
@@ -31755,7 +32901,7 @@ function Group_applyDrcTolerancesToSimpleRouteJson(simpleRouteJson, drcTolerance
31755
32901
 
31756
32902
  // lib/components/primitive-components/Group/Group_syncFanoutExitsWithGlobalConnections.ts
31757
32903
  var POINT_MATCH_TOLERANCE = 1e-6;
31758
- var pointsMatch = (first, second) => Math.abs(first.x - second.x) <= POINT_MATCH_TOLERANCE && Math.abs(first.y - second.y) <= POINT_MATCH_TOLERANCE && first.layer === second.layer;
32904
+ var pointsMatch2 = (first, second) => Math.abs(first.x - second.x) <= POINT_MATCH_TOLERANCE && Math.abs(first.y - second.y) <= POINT_MATCH_TOLERANCE && first.layer === second.layer;
31759
32905
  var removeCompletedFanoutConnections = (simpleRouteJson, routingPcbGroupId) => {
31760
32906
  const connections = simpleRouteJson.connections.filter(
31761
32907
  (connection) => connection.routingPcbGroupId !== routingPcbGroupId
@@ -31827,14 +32973,14 @@ function Group_syncFanoutExitsWithGlobalConnections({
31827
32973
  const globalConnection = baseConnections.find(
31828
32974
  (connection) => connection.routingPcbGroupId !== routingPcbGroupId && connection.source_trace_id === inputConnection.source_trace_id && connection.pointsToConnect.some(
31829
32975
  (globalPoint) => inputConnection.pointsToConnect.some(
31830
- (inputPoint) => pointsMatch(globalPoint, inputPoint)
32976
+ (inputPoint) => pointsMatch2(globalPoint, inputPoint)
31831
32977
  )
31832
32978
  )
31833
32979
  );
31834
32980
  if (!globalConnection) continue;
31835
32981
  const previousGlobalPointIndex = globalConnection.pointsToConnect.findIndex(
31836
32982
  (globalPoint) => inputConnection.pointsToConnect.some(
31837
- (inputPoint) => pointsMatch(globalPoint, inputPoint)
32983
+ (inputPoint) => pointsMatch2(globalPoint, inputPoint)
31838
32984
  )
31839
32985
  );
31840
32986
  if (previousGlobalPointIndex < 0) continue;
@@ -31842,7 +32988,7 @@ function Group_syncFanoutExitsWithGlobalConnections({
31842
32988
  const changedPointIndex = outputConnection.pointsToConnect.findIndex(
31843
32989
  (outputPoint, pointIndex) => {
31844
32990
  const inputPoint = inputConnection.pointsToConnect[pointIndex];
31845
- return inputPoint !== void 0 && !pointsMatch(outputPoint, inputPoint);
32991
+ return inputPoint !== void 0 && !pointsMatch2(outputPoint, inputPoint);
31846
32992
  }
31847
32993
  );
31848
32994
  if (changedPointIndex < 0) continue;
@@ -40195,15 +41341,15 @@ import {
40195
41341
  } from "@tscircuit/copper-pour-solver";
40196
41342
 
40197
41343
  // lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
40198
- var EPSILON3 = 1e-9;
41344
+ var EPSILON4 = 1e-9;
40199
41345
  var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
40200
41346
  var isPointOnSegment = (p, a, b) => {
40201
41347
  const cross3 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
40202
- if (Math.abs(cross3) > EPSILON3) return false;
41348
+ if (Math.abs(cross3) > EPSILON4) return false;
40203
41349
  const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
40204
- if (dot < -EPSILON3) return false;
41350
+ if (dot < -EPSILON4) return false;
40205
41351
  const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
40206
- if (dot - squaredLength > EPSILON3) return false;
41352
+ if (dot - squaredLength > EPSILON4) return false;
40207
41353
  return true;
40208
41354
  };
40209
41355
  var isPointInRing = (point6, ring) => {
@@ -40227,7 +41373,7 @@ var isPointInRectPour = (p, pour) => {
40227
41373
  const dy = p.y - center.y;
40228
41374
  const localX = dx * cosR - dy * sinR;
40229
41375
  const localY = dx * sinR + dy * cosR;
40230
- return Math.abs(localX) <= width / 2 + EPSILON3 && Math.abs(localY) <= height / 2 + EPSILON3;
41376
+ return Math.abs(localX) <= width / 2 + EPSILON4 && Math.abs(localY) <= height / 2 + EPSILON4;
40231
41377
  };
40232
41378
  var isPointInBrepPour = (p, pour) => {
40233
41379
  const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
@@ -40261,7 +41407,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
40261
41407
  const dx = end.x - start.x;
40262
41408
  const dy = end.y - start.y;
40263
41409
  const length7 = Math.hypot(dx, dy);
40264
- if (length7 <= EPSILON3) return false;
41410
+ if (length7 <= EPSILON4) return false;
40265
41411
  const samples = [0, 0.25, 0.5, 0.75, 1];
40266
41412
  return samples.every(
40267
41413
  (t) => isPointInCopperPour(