@tscircuit/schematic-trace-solver 0.0.112 → 0.0.114

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 (22) hide show
  1. package/dist/index.d.ts +10 -0
  2. package/dist/index.js +128 -10
  3. package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +55 -1
  4. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +13 -4
  5. package/lib/solvers/MspConnectionPairSolver/isLabeledPeripheralConnection.ts +44 -0
  6. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -3
  7. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +11 -1
  8. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +12 -0
  9. package/package.json +1 -1
  10. package/site/bug-reports/bug-report-20260728T144234Z.page.tsx +4 -0
  11. package/site/bug-reports/bug-report-20260728T225606Z.page.tsx +4 -0
  12. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +1 -1
  13. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +108 -0
  14. package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.json +518 -0
  15. package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.test.ts +12 -0
  16. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +131 -0
  17. package/tests/bug-reports/bug-report-20260728T225606Z/bug-report-20260728T225606Z.json +1127 -0
  18. package/tests/bug-reports/bug-report-20260728T225606Z/bug-report-20260728T225606Z.test.ts +35 -0
  19. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +1 -1
  20. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +70 -0
  21. package/tests/repros/assets/repro-rp2040-usb-cc2-ground-overlap.input.json +164 -0
  22. package/tests/repros/repro-rp2040-usb-cc2-ground-overlap.test.ts +95 -0
package/dist/index.d.ts CHANGED
@@ -244,6 +244,7 @@ declare class TraceOverlapIssueSolver extends BaseSolver {
244
244
  overlappingTraceSegments: OverlappingTraceSegmentLocator[];
245
245
  interactionKind: TraceInteractionKind;
246
246
  traceNetIslands: Record<ConnNetId$1, Array<SolvedTracePath>>;
247
+ traceIdsToShift?: Set<MspConnectionPairId>;
247
248
  obstacleRects: ReturnType<typeof getObstacleRects>;
248
249
  SHIFT_DISTANCE: number;
249
250
  correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath>;
@@ -252,6 +253,7 @@ declare class TraceOverlapIssueSolver extends BaseSolver {
252
253
  overlappingTraceSegments: OverlappingTraceSegmentLocator[];
253
254
  interactionKind: TraceInteractionKind;
254
255
  traceNetIslands: Record<ConnNetId$1, Array<SolvedTracePath>>;
256
+ traceIdsToShift?: Set<MspConnectionPairId>;
255
257
  });
256
258
  _step(): void;
257
259
  private applyOffsets;
@@ -284,6 +286,7 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
284
286
  inputProblem: InputProblem;
285
287
  inputTracePaths: Array<SolvedTracePath>;
286
288
  globalConnMap: ConnectivityMap;
289
+ traceIdsToShift?: Set<MspConnectionPairId>;
287
290
  activeSubSolver: TraceOverlapIssueSolver | null;
288
291
  /**
289
292
  * A traceNetIsland is a set of traces that are connected via the globalConnMap
@@ -295,6 +298,7 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
295
298
  inputProblem: InputProblem;
296
299
  inputTracePaths: Array<SolvedTracePath>;
297
300
  globalConnMap: ConnectivityMap;
301
+ traceIdsToShift?: Set<MspConnectionPairId>;
298
302
  });
299
303
  getConstructorParams(): ConstructorParameters<typeof TraceOverlapShiftSolver>[0];
300
304
  computeTraceNetIslands(): Record<ConnNetId, Array<SolvedTracePath>>;
@@ -582,6 +586,8 @@ declare class LongDistancePairSolver extends BaseSolver {
582
586
  solvedLongDistanceTraces: SolvedTracePath[];
583
587
  private queuedCandidatePairs;
584
588
  private currentCandidatePair;
589
+ private queuedFailedConnectionPairs;
590
+ private currentFailedConnectionPair;
585
591
  private subSolver;
586
592
  private chipMap;
587
593
  private inputProblem;
@@ -592,12 +598,15 @@ declare class LongDistancePairSolver extends BaseSolver {
592
598
  inputProblem: InputProblem;
593
599
  alreadySolvedTraces: SolvedTracePath[];
594
600
  primaryMspConnectionPairs: MspConnectionPair[];
601
+ failedConnectionPairs: MspConnectionPair[];
595
602
  });
596
603
  getConstructorParams(): {
597
604
  inputProblem: InputProblem;
598
605
  alreadySolvedTraces: SolvedTracePath[];
599
606
  primaryMspConnectionPairs: MspConnectionPair[];
607
+ failedConnectionPairs: MspConnectionPair[];
600
608
  };
609
+ private acceptFailedConnectionPair;
601
610
  _step(): void;
602
611
  visualize(): graphics_debug.GraphicsObject;
603
612
  getOutput(): {
@@ -1071,6 +1080,7 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1071
1080
  traceCleanupSolver?: TraceCleanupSolver;
1072
1081
  example28Solver?: Example28Solver;
1073
1082
  availableNetOrientationSolver?: AvailableNetOrientationSolver;
1083
+ postLabelTraceOverlapShiftSolver?: TraceOverlapShiftSolver;
1074
1084
  railNetLabelCornerPlacementSolver?: RailNetLabelCornerPlacementSolver;
1075
1085
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver;
1076
1086
  preAlignmentNetLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
package/dist/index.js CHANGED
@@ -420,6 +420,33 @@ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
420
420
  return edges;
421
421
  }
422
422
 
423
+ // lib/solvers/MspConnectionPairSolver/isLabeledPeripheralConnection.ts
424
+ var isLabeledPeripheralConnection = ({
425
+ inputProblem,
426
+ chipMap,
427
+ pins
428
+ }) => {
429
+ const [firstPin, secondPin] = pins;
430
+ const directConnection = inputProblem.directConnections.find(
431
+ (connection) => connection.pinIds.includes(firstPin.pinId) && connection.pinIds.includes(secondPin.pinId)
432
+ );
433
+ if (directConnection?.netLabelWidth === void 0) return false;
434
+ const firstChip = chipMap[firstPin.chipId];
435
+ const secondChip = chipMap[secondPin.chipId];
436
+ if (!firstChip || !secondChip) return false;
437
+ const hasSinglePinPeripheral = firstChip.pins.length === 1 || secondChip.pins.length === 1;
438
+ if (!hasSinglePinPeripheral) return false;
439
+ let firstFacingDirection = firstPin._facingDirection;
440
+ if (!firstFacingDirection) {
441
+ firstFacingDirection = getPinDirection(firstPin, firstChip);
442
+ }
443
+ let secondFacingDirection = secondPin._facingDirection;
444
+ if (!secondFacingDirection) {
445
+ secondFacingDirection = getPinDirection(secondPin, secondChip);
446
+ }
447
+ return firstFacingDirection === "x-" && secondFacingDirection === "x+" || firstFacingDirection === "x+" && secondFacingDirection === "x-";
448
+ };
449
+
423
450
  // lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
424
451
  var DEFAULT_MAX_MSP_PAIR_DISTANCE = 1;
425
452
  var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
@@ -489,8 +516,16 @@ var MspConnectionPairSolver = class extends BaseSolver {
489
516
  const p1 = this.pinMap[pin1];
490
517
  const p2 = this.pinMap[pin2];
491
518
  const pinPairKey = getPinPairKey([pin1, pin2]);
492
- const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey) ? distance(p1, p2) : Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
493
- if (pairDistance > this.maxMspPairDistance) {
519
+ let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
520
+ if (this.directConnectionPinPairKeys.has(pinPairKey)) {
521
+ pairDistance = distance(p1, p2);
522
+ }
523
+ const isLabeledPeripheral = isLabeledPeripheralConnection({
524
+ inputProblem: this.inputProblem,
525
+ chipMap: this.chipMap,
526
+ pins: [p1, p2]
527
+ });
528
+ if (pairDistance > this.maxMspPairDistance && !isLabeledPeripheral) {
494
529
  return;
495
530
  }
496
531
  if (arePinsInDifferentSchematicSections(this.inputProblem, p1, p2)) {
@@ -1744,6 +1779,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1744
1779
  overlappingTraceSegments;
1745
1780
  interactionKind;
1746
1781
  traceNetIslands;
1782
+ traceIdsToShift;
1747
1783
  obstacleRects;
1748
1784
  SHIFT_DISTANCE = 0.1;
1749
1785
  correctedTraceMap = {};
@@ -1752,6 +1788,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1752
1788
  this.overlappingTraceSegments = params.overlappingTraceSegments;
1753
1789
  this.interactionKind = params.interactionKind;
1754
1790
  this.traceNetIslands = params.traceNetIslands;
1791
+ this.traceIdsToShift = params.traceIdsToShift;
1755
1792
  this.obstacleRects = getObstacleRects(params.inputProblem);
1756
1793
  for (const { connNetId, pathsWithOverlap } of this.overlappingTraceSegments) {
1757
1794
  for (const {
@@ -1769,7 +1806,13 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1769
1806
  return path.tracePath.length === 2;
1770
1807
  });
1771
1808
  const groupShouldStayInPlace = this.overlappingTraceSegments.map(
1772
- containsStraightPinToPinTrace
1809
+ (group) => {
1810
+ if (!this.traceIdsToShift) return containsStraightPinToPinTrace(group);
1811
+ return group.pathsWithOverlap.every(({ solvedTracePathIndex }) => {
1812
+ const path = this.traceNetIslands[group.connNetId][solvedTracePathIndex];
1813
+ return !this.traceIdsToShift.has(path.mspPairId);
1814
+ });
1815
+ }
1773
1816
  );
1774
1817
  const someGroupCanShift = groupShouldStayInPlace.some(
1775
1818
  (shouldStay) => !shouldStay
@@ -2049,6 +2092,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2049
2092
  inputProblem;
2050
2093
  inputTracePaths;
2051
2094
  globalConnMap;
2095
+ traceIdsToShift;
2052
2096
  /**
2053
2097
  * A traceNetIsland is a set of traces that are connected via the globalConnMap
2054
2098
  */
@@ -2060,6 +2104,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2060
2104
  this.inputProblem = params.inputProblem;
2061
2105
  this.inputTracePaths = params.inputTracePaths;
2062
2106
  this.globalConnMap = params.globalConnMap;
2107
+ this.traceIdsToShift = params.traceIdsToShift;
2063
2108
  for (const tracePath of this.inputTracePaths) {
2064
2109
  const { mspPairId } = tracePath;
2065
2110
  this.correctedTraceMap[mspPairId] = tracePath;
@@ -2070,7 +2115,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2070
2115
  return {
2071
2116
  inputProblem: this.inputProblem,
2072
2117
  inputTracePaths: this.inputTracePaths,
2073
- globalConnMap: this.globalConnMap
2118
+ globalConnMap: this.globalConnMap,
2119
+ traceIdsToShift: this.traceIdsToShift
2074
2120
  };
2075
2121
  }
2076
2122
  computeTraceNetIslands() {
@@ -2158,6 +2204,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2158
2204
  if (!aVert && !aHorz) continue;
2159
2205
  for (let pb = 0; pb < pathsB.length; pb++) {
2160
2206
  const pathB = pathsB[pb];
2207
+ if (this.traceIdsToShift && !this.traceIdsToShift.has(pathA.mspPairId) && !this.traceIdsToShift.has(pathB.mspPairId)) {
2208
+ continue;
2209
+ }
2161
2210
  const ptsB = pathB.tracePath;
2162
2211
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
2163
2212
  const b1 = ptsB[sb];
@@ -2307,7 +2356,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2307
2356
  inputProblem: this.inputProblem,
2308
2357
  interactionKind,
2309
2358
  overlappingTraceSegments,
2310
- traceNetIslands: this.traceNetIslands
2359
+ traceNetIslands: this.traceNetIslands,
2360
+ traceIdsToShift: this.traceIdsToShift
2311
2361
  });
2312
2362
  }
2313
2363
  visualize() {
@@ -4480,6 +4530,13 @@ var LongDistancePairSolver = class extends BaseSolver {
4480
4530
  pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
4481
4531
  }
4482
4532
  }
4533
+ this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
4534
+ (connectionPair) => isLabeledPeripheralConnection({
4535
+ inputProblem: this.inputProblem,
4536
+ chipMap: this.chipMap,
4537
+ pins: connectionPair.pins
4538
+ })
4539
+ );
4483
4540
  const candidatePairs = [];
4484
4541
  const addedPairKeys = /* @__PURE__ */ new Set();
4485
4542
  for (const netId of Object.keys(netConnMap.netMap)) {
@@ -4520,6 +4577,8 @@ var LongDistancePairSolver = class extends BaseSolver {
4520
4577
  solvedLongDistanceTraces = [];
4521
4578
  queuedCandidatePairs = [];
4522
4579
  currentCandidatePair = null;
4580
+ queuedFailedConnectionPairs = [];
4581
+ currentFailedConnectionPair = null;
4523
4582
  subSolver = null;
4524
4583
  chipMap = {};
4525
4584
  inputProblem;
@@ -4529,8 +4588,31 @@ var LongDistancePairSolver = class extends BaseSolver {
4529
4588
  getConstructorParams() {
4530
4589
  return this.params;
4531
4590
  }
4591
+ acceptFailedConnectionPair(tracePath) {
4592
+ const connectionPair = this.currentFailedConnectionPair;
4593
+ if (!connectionPair) return;
4594
+ const solvedTrace = {
4595
+ ...connectionPair,
4596
+ tracePath,
4597
+ mspConnectionPairIds: [connectionPair.mspPairId],
4598
+ pinIds: connectionPair.pins.map((pin) => pin.pinId)
4599
+ };
4600
+ this.solvedLongDistanceTraces.push(solvedTrace);
4601
+ this.allSolvedTraces.push(solvedTrace);
4602
+ for (const pin of connectionPair.pins) {
4603
+ this.newlyConnectedPinIds.add(pin.pinId);
4604
+ }
4605
+ }
4532
4606
  _step() {
4533
- if (this.subSolver?.solved) {
4607
+ if (this.subSolver?.solved && this.currentFailedConnectionPair) {
4608
+ const tracePath = this.subSolver.solvedTracePath;
4609
+ if (tracePath) {
4610
+ this.acceptFailedConnectionPair(tracePath);
4611
+ }
4612
+ this.subSolver = null;
4613
+ this.currentCandidatePair = null;
4614
+ this.currentFailedConnectionPair = null;
4615
+ } else if (this.subSolver?.solved) {
4534
4616
  const newTracePath = this.subSolver.solvedTracePath;
4535
4617
  if (newTracePath && this.currentCandidatePair) {
4536
4618
  const isTraceClear = !doesTraceOverlapWithExistingTraces(
@@ -4561,11 +4643,23 @@ var LongDistancePairSolver = class extends BaseSolver {
4561
4643
  } else if (this.subSolver?.failed) {
4562
4644
  this.subSolver = null;
4563
4645
  this.currentCandidatePair = null;
4646
+ this.currentFailedConnectionPair = null;
4564
4647
  }
4565
4648
  if (this.subSolver) {
4566
4649
  this.subSolver.step();
4567
4650
  return;
4568
4651
  }
4652
+ const failedConnectionPair = this.queuedFailedConnectionPairs.shift();
4653
+ if (failedConnectionPair) {
4654
+ this.currentFailedConnectionPair = failedConnectionPair;
4655
+ this.currentCandidatePair = failedConnectionPair.pins;
4656
+ this.subSolver = new SchematicTraceSingleLineSolver2({
4657
+ inputProblem: this.params.inputProblem,
4658
+ pins: failedConnectionPair.pins,
4659
+ chipMap: this.chipMap
4660
+ });
4661
+ return;
4662
+ }
4569
4663
  while (this.queuedCandidatePairs.length > 0) {
4570
4664
  const nextPair = this.queuedCandidatePairs.shift();
4571
4665
  const [p1, p2] = nextPair;
@@ -10632,6 +10726,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10632
10726
  traceCleanupSolver;
10633
10727
  example28Solver;
10634
10728
  availableNetOrientationSolver;
10729
+ postLabelTraceOverlapShiftSolver;
10635
10730
  railNetLabelCornerPlacementSolver;
10636
10731
  traceAnchoredNetLabelOverlapSolver;
10637
10732
  preAlignmentNetLabelTraceCollisionSolver;
@@ -10687,7 +10782,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10687
10782
  {
10688
10783
  inputProblem: instance.inputProblem,
10689
10784
  primaryMspConnectionPairs: instance.mspConnectionPairSolver.mspConnectionPairs,
10690
- alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths
10785
+ alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths,
10786
+ failedConnectionPairs: instance.schematicTraceLinesSolver.failedConnectionPairs
10691
10787
  }
10692
10788
  ],
10693
10789
  {
@@ -10807,6 +10903,22 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10807
10903
  }
10808
10904
  ]
10809
10905
  ),
10906
+ definePipelineStep(
10907
+ "postLabelTraceOverlapShiftSolver",
10908
+ TraceOverlapShiftSolver,
10909
+ (instance) => [
10910
+ {
10911
+ inputProblem: instance.inputProblem,
10912
+ inputTracePaths: instance.availableNetOrientationSolver.traces,
10913
+ globalConnMap: instance.mspConnectionPairSolver.globalConnMap,
10914
+ traceIdsToShift: new Set(
10915
+ instance.availableNetOrientationSolver.traces.filter(
10916
+ (trace) => trace.mspPairId.startsWith("available-net-orientation-")
10917
+ ).map((trace) => trace.mspPairId)
10918
+ )
10919
+ }
10920
+ ]
10921
+ ),
10810
10922
  definePipelineStep(
10811
10923
  "railNetLabelCornerPlacementSolver",
10812
10924
  RailNetLabelCornerPlacementSolver,
@@ -10814,7 +10926,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10814
10926
  return [
10815
10927
  {
10816
10928
  inputProblem: instance.inputProblem,
10817
- traces: instance.availableNetOrientationSolver.traces,
10929
+ traces: Object.values(
10930
+ instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
10931
+ ),
10818
10932
  netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements
10819
10933
  }
10820
10934
  ];
@@ -10826,7 +10940,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10826
10940
  (instance) => [
10827
10941
  {
10828
10942
  inputProblem: instance.inputProblem,
10829
- traces: instance.availableNetOrientationSolver.traces,
10943
+ traces: Object.values(
10944
+ instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
10945
+ ),
10830
10946
  netLabelPlacements: instance.railNetLabelCornerPlacementSolver.outputNetLabelPlacements
10831
10947
  }
10832
10948
  ]
@@ -10837,7 +10953,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10837
10953
  (instance) => [
10838
10954
  {
10839
10955
  inputProblem: instance.inputProblem,
10840
- traces: instance.availableNetOrientationSolver.traces,
10956
+ traces: Object.values(
10957
+ instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
10958
+ ),
10841
10959
  netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
10842
10960
  }
10843
10961
  ]
@@ -13,6 +13,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
13
13
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
14
14
  import type { ConnectivityMap } from "connectivity-map"
15
15
  import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
16
+ import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
16
17
 
17
18
  const NEAREST_NEIGHBOR_COUNT = 3
18
19
 
@@ -28,6 +29,8 @@ export class LongDistancePairSolver extends BaseSolver {
28
29
  private currentCandidatePair:
29
30
  | [InputPin & { chipId: string }, InputPin & { chipId: string }]
30
31
  | null = null
32
+ private queuedFailedConnectionPairs: MspConnectionPair[] = []
33
+ private currentFailedConnectionPair: MspConnectionPair | null = null
31
34
  private subSolver: SchematicTraceSingleLineSolver2 | null = null
32
35
  private chipMap: Record<string, InputChip> = {}
33
36
  private inputProblem: InputProblem
@@ -40,6 +43,7 @@ export class LongDistancePairSolver extends BaseSolver {
40
43
  inputProblem: InputProblem
41
44
  alreadySolvedTraces: SolvedTracePath[]
42
45
  primaryMspConnectionPairs: MspConnectionPair[]
46
+ failedConnectionPairs: MspConnectionPair[]
43
47
  },
44
48
  ) {
45
49
  super()
@@ -66,6 +70,16 @@ export class LongDistancePairSolver extends BaseSolver {
66
70
  pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId })
67
71
  }
68
72
  }
73
+ // Retry failed MSP pairs with their existing identity before creating
74
+ // new nearest-neighbor candidates.
75
+ this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
76
+ (connectionPair) =>
77
+ isLabeledPeripheralConnection({
78
+ inputProblem: this.inputProblem,
79
+ chipMap: this.chipMap,
80
+ pins: connectionPair.pins,
81
+ }),
82
+ )
69
83
 
70
84
  // 2. Generate candidate pairs using N-Nearest-Neighbors approach
71
85
  const candidatePairs: Array<
@@ -129,9 +143,36 @@ export class LongDistancePairSolver extends BaseSolver {
129
143
  return this.params
130
144
  }
131
145
 
146
+ private acceptFailedConnectionPair(tracePath: SolvedTracePath["tracePath"]) {
147
+ const connectionPair = this.currentFailedConnectionPair
148
+ if (!connectionPair) return
149
+
150
+ // Reuse the original pair so downstream solvers retain its connectivity
151
+ // IDs, user net ID, pins, and MSP pair ID.
152
+ const solvedTrace: SolvedTracePath = {
153
+ ...connectionPair,
154
+ tracePath,
155
+ mspConnectionPairIds: [connectionPair.mspPairId],
156
+ pinIds: connectionPair.pins.map((pin) => pin.pinId),
157
+ }
158
+ this.solvedLongDistanceTraces.push(solvedTrace)
159
+ this.allSolvedTraces.push(solvedTrace)
160
+ for (const pin of connectionPair.pins) {
161
+ this.newlyConnectedPinIds.add(pin.pinId)
162
+ }
163
+ }
164
+
132
165
  override _step() {
133
166
  // 1. Check if a sub-solver has finished and process its result
134
- if (this.subSolver?.solved) {
167
+ if (this.subSolver?.solved && this.currentFailedConnectionPair) {
168
+ const tracePath = this.subSolver.solvedTracePath
169
+ if (tracePath) {
170
+ this.acceptFailedConnectionPair(tracePath)
171
+ }
172
+ this.subSolver = null
173
+ this.currentCandidatePair = null
174
+ this.currentFailedConnectionPair = null
175
+ } else if (this.subSolver?.solved) {
135
176
  const newTracePath = this.subSolver.solvedTracePath
136
177
  if (newTracePath && this.currentCandidatePair) {
137
178
  const isTraceClear = !doesTraceOverlapWithExistingTraces(
@@ -166,6 +207,7 @@ export class LongDistancePairSolver extends BaseSolver {
166
207
  } else if (this.subSolver?.failed) {
167
208
  this.subSolver = null
168
209
  this.currentCandidatePair = null
210
+ this.currentFailedConnectionPair = null
169
211
  }
170
212
 
171
213
  // 2. If a sub-solver is already running, let it continue
@@ -174,6 +216,18 @@ export class LongDistancePairSolver extends BaseSolver {
174
216
  return
175
217
  }
176
218
 
219
+ const failedConnectionPair = this.queuedFailedConnectionPairs.shift()
220
+ if (failedConnectionPair) {
221
+ this.currentFailedConnectionPair = failedConnectionPair
222
+ this.currentCandidatePair = failedConnectionPair.pins
223
+ this.subSolver = new SchematicTraceSingleLineSolver2({
224
+ inputProblem: this.params.inputProblem,
225
+ pins: failedConnectionPair.pins,
226
+ chipMap: this.chipMap,
227
+ })
228
+ return
229
+ }
230
+
177
231
  // 3. Find the next valid candidate pair and start a new sub-solver
178
232
  while (this.queuedCandidatePairs.length > 0) {
179
233
  const nextPair = this.queuedCandidatePairs.shift()!
@@ -14,6 +14,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
14
14
  import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
15
15
  import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
16
16
  import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
17
+ import { isLabeledPeripheralConnection } from "./isLabeledPeripheralConnection"
17
18
 
18
19
  export type MspConnectionPairId = string
19
20
  export const DEFAULT_MAX_MSP_PAIR_DISTANCE = 1
@@ -116,10 +117,18 @@ export class MspConnectionPairSolver extends BaseSolver {
116
117
  const pinPairKey = getPinPairKey([pin1!, pin2!])
117
118
  // Explicit source traces are classified by straight-line distance when
118
119
  // their input is created; named nets retain the orthogonal route metric.
119
- const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey)
120
- ? distance(p1, p2)
121
- : Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
122
- if (pairDistance > this.maxMspPairDistance) {
120
+ let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
121
+ if (this.directConnectionPinPairKeys.has(pinPairKey)) {
122
+ pairDistance = distance(p1, p2)
123
+ }
124
+ // Labeled one-pin peripherals need a real trace even when they are far
125
+ // apart; skipping the MSP pair would leave only the fallback path.
126
+ const isLabeledPeripheral = isLabeledPeripheralConnection({
127
+ inputProblem: this.inputProblem,
128
+ chipMap: this.chipMap,
129
+ pins: [p1, p2],
130
+ })
131
+ if (pairDistance > this.maxMspPairDistance && !isLabeledPeripheral) {
123
132
  // Too far apart; skip creating an MSP pair for this net
124
133
  return
125
134
  }
@@ -0,0 +1,44 @@
1
+ import type { InputChip, InputPin, InputProblem } from "lib/types/InputProblem"
2
+ import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
3
+
4
+ type PinWithChipId = InputPin & { chipId: string }
5
+
6
+ export const isLabeledPeripheralConnection = ({
7
+ inputProblem,
8
+ chipMap,
9
+ pins,
10
+ }: {
11
+ inputProblem: InputProblem
12
+ chipMap: Record<string, InputChip>
13
+ pins: [PinWithChipId, PinWithChipId]
14
+ }): boolean => {
15
+ const [firstPin, secondPin] = pins
16
+ const directConnection = inputProblem.directConnections.find(
17
+ (connection) =>
18
+ connection.pinIds.includes(firstPin.pinId) &&
19
+ connection.pinIds.includes(secondPin.pinId),
20
+ )
21
+ if (directConnection?.netLabelWidth === undefined) return false
22
+
23
+ const firstChip = chipMap[firstPin.chipId]
24
+ const secondChip = chipMap[secondPin.chipId]
25
+ if (!firstChip || !secondChip) return false
26
+
27
+ const hasSinglePinPeripheral =
28
+ firstChip.pins.length === 1 || secondChip.pins.length === 1
29
+ if (!hasSinglePinPeripheral) return false
30
+
31
+ let firstFacingDirection = firstPin._facingDirection
32
+ if (!firstFacingDirection) {
33
+ firstFacingDirection = getPinDirection(firstPin, firstChip)
34
+ }
35
+ let secondFacingDirection = secondPin._facingDirection
36
+ if (!secondFacingDirection) {
37
+ secondFacingDirection = getPinDirection(secondPin, secondChip)
38
+ }
39
+
40
+ return (
41
+ (firstFacingDirection === "x-" && secondFacingDirection === "x+") ||
42
+ (firstFacingDirection === "x+" && secondFacingDirection === "x-")
43
+ )
44
+ }
@@ -79,6 +79,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
79
79
  traceCleanupSolver?: TraceCleanupSolver
80
80
  example28Solver?: Example28Solver
81
81
  availableNetOrientationSolver?: AvailableNetOrientationSolver
82
+ postLabelTraceOverlapShiftSolver?: TraceOverlapShiftSolver
82
83
  railNetLabelCornerPlacementSolver?: RailNetLabelCornerPlacementSolver
83
84
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
84
85
  preAlignmentNetLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
@@ -139,6 +140,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
139
140
  instance.mspConnectionPairSolver!.mspConnectionPairs,
140
141
  alreadySolvedTraces:
141
142
  instance.schematicTraceLinesSolver!.solvedTracePaths,
143
+ failedConnectionPairs:
144
+ instance.schematicTraceLinesSolver!.failedConnectionPairs,
142
145
  },
143
146
  ],
144
147
  {
@@ -280,6 +283,24 @@ export class SchematicTracePipelineSolver extends BaseSolver {
280
283
  },
281
284
  ],
282
285
  ),
286
+ definePipelineStep(
287
+ "postLabelTraceOverlapShiftSolver",
288
+ TraceOverlapShiftSolver,
289
+ (instance) => [
290
+ {
291
+ inputProblem: instance.inputProblem,
292
+ inputTracePaths: instance.availableNetOrientationSolver!.traces,
293
+ globalConnMap: instance.mspConnectionPairSolver!.globalConnMap,
294
+ traceIdsToShift: new Set(
295
+ instance
296
+ .availableNetOrientationSolver!.traces.filter((trace) =>
297
+ trace.mspPairId.startsWith("available-net-orientation-"),
298
+ )
299
+ .map((trace) => trace.mspPairId),
300
+ ),
301
+ },
302
+ ],
303
+ ),
283
304
  definePipelineStep(
284
305
  "railNetLabelCornerPlacementSolver",
285
306
  RailNetLabelCornerPlacementSolver,
@@ -287,7 +308,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
287
308
  return [
288
309
  {
289
310
  inputProblem: instance.inputProblem,
290
- traces: instance.availableNetOrientationSolver!.traces,
311
+ traces: Object.values(
312
+ instance.postLabelTraceOverlapShiftSolver!.correctedTraceMap,
313
+ ),
291
314
  netLabelPlacements:
292
315
  instance.availableNetOrientationSolver!.outputNetLabelPlacements,
293
316
  },
@@ -300,7 +323,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
300
323
  (instance) => [
301
324
  {
302
325
  inputProblem: instance.inputProblem,
303
- traces: instance.availableNetOrientationSolver!.traces,
326
+ traces: Object.values(
327
+ instance.postLabelTraceOverlapShiftSolver!.correctedTraceMap,
328
+ ),
304
329
  netLabelPlacements:
305
330
  instance.railNetLabelCornerPlacementSolver!
306
331
  .outputNetLabelPlacements,
@@ -313,7 +338,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
313
338
  (instance) => [
314
339
  {
315
340
  inputProblem: instance.inputProblem,
316
- traces: instance.availableNetOrientationSolver!.traces,
341
+ traces: Object.values(
342
+ instance.postLabelTraceOverlapShiftSolver!.correctedTraceMap,
343
+ ),
317
344
  netLabelPlacements:
318
345
  instance.traceAnchoredNetLabelOverlapSolver!
319
346
  .outputNetLabelPlacements,
@@ -26,6 +26,7 @@ export class TraceOverlapIssueSolver extends BaseSolver {
26
26
  overlappingTraceSegments: OverlappingTraceSegmentLocator[]
27
27
  interactionKind: TraceInteractionKind
28
28
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
29
+ traceIdsToShift?: Set<MspConnectionPairId>
29
30
  obstacleRects: ReturnType<typeof getObstacleRects>
30
31
 
31
32
  SHIFT_DISTANCE = 0.1
@@ -37,11 +38,13 @@ export class TraceOverlapIssueSolver extends BaseSolver {
37
38
  overlappingTraceSegments: OverlappingTraceSegmentLocator[]
38
39
  interactionKind: TraceInteractionKind
39
40
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
41
+ traceIdsToShift?: Set<MspConnectionPairId>
40
42
  }) {
41
43
  super()
42
44
  this.overlappingTraceSegments = params.overlappingTraceSegments
43
45
  this.interactionKind = params.interactionKind
44
46
  this.traceNetIslands = params.traceNetIslands
47
+ this.traceIdsToShift = params.traceIdsToShift
45
48
  this.obstacleRects = getObstacleRects(params.inputProblem)
46
49
 
47
50
  // Only add the relevant traces to the correctedTraceMap
@@ -76,7 +79,14 @@ export class TraceOverlapIssueSolver extends BaseSolver {
76
79
  })
77
80
 
78
81
  const groupShouldStayInPlace = this.overlappingTraceSegments.map(
79
- containsStraightPinToPinTrace,
82
+ (group) => {
83
+ if (!this.traceIdsToShift) return containsStraightPinToPinTrace(group)
84
+ return group.pathsWithOverlap.every(({ solvedTracePathIndex }) => {
85
+ const path =
86
+ this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
87
+ return !this.traceIdsToShift!.has(path.mspPairId)
88
+ })
89
+ },
80
90
  )
81
91
  const someGroupCanShift = groupShouldStayInPlace.some(
82
92
  (shouldStay) => !shouldStay,
@@ -34,6 +34,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
34
34
  inputProblem: InputProblem
35
35
  inputTracePaths: Array<SolvedTracePath>
36
36
  globalConnMap: ConnectivityMap
37
+ traceIdsToShift?: Set<MspConnectionPairId>
37
38
 
38
39
  declare activeSubSolver: TraceOverlapIssueSolver | null
39
40
 
@@ -50,11 +51,13 @@ export class TraceOverlapShiftSolver extends BaseSolver {
50
51
  inputProblem: InputProblem
51
52
  inputTracePaths: Array<SolvedTracePath>
52
53
  globalConnMap: ConnectivityMap
54
+ traceIdsToShift?: Set<MspConnectionPairId>
53
55
  }) {
54
56
  super()
55
57
  this.inputProblem = params.inputProblem
56
58
  this.inputTracePaths = params.inputTracePaths
57
59
  this.globalConnMap = params.globalConnMap
60
+ this.traceIdsToShift = params.traceIdsToShift
58
61
 
59
62
  for (const tracePath of this.inputTracePaths) {
60
63
  const { mspPairId } = tracePath
@@ -71,6 +74,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
71
74
  inputProblem: this.inputProblem,
72
75
  inputTracePaths: this.inputTracePaths,
73
76
  globalConnMap: this.globalConnMap,
77
+ traceIdsToShift: this.traceIdsToShift,
74
78
  }
75
79
  }
76
80
 
@@ -211,6 +215,13 @@ export class TraceOverlapShiftSolver extends BaseSolver {
211
215
 
212
216
  for (let pb = 0; pb < pathsB.length; pb++) {
213
217
  const pathB = pathsB[pb]!
218
+ if (
219
+ this.traceIdsToShift &&
220
+ !this.traceIdsToShift.has(pathA.mspPairId) &&
221
+ !this.traceIdsToShift.has(pathB.mspPairId)
222
+ ) {
223
+ continue
224
+ }
214
225
  const ptsB = pathB.tracePath
215
226
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
216
227
  const b1 = ptsB[sb]!
@@ -392,6 +403,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
392
403
  interactionKind,
393
404
  overlappingTraceSegments,
394
405
  traceNetIslands: this.traceNetIslands,
406
+ traceIdsToShift: this.traceIdsToShift,
395
407
  })
396
408
  }
397
409