@tscircuit/schematic-trace-solver 0.0.157 → 0.0.159

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 (175) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +269 -58
  3. package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +15 -8
  4. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +5 -0
  5. package/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +83 -0
  6. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +6 -1
  7. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +196 -67
  8. package/lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts +122 -0
  9. package/package.json +1 -1
  10. package/site/bug-reports/bug-report-20260826T072956Z.page.tsx +4 -0
  11. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +2 -2
  12. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +2 -2
  13. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +2 -2
  14. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +2 -2
  15. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +2 -2
  17. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +2 -2
  18. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +2 -2
  19. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +2 -2
  20. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  21. package/tests/bug-reports/bug-report-20260708T053736Z/__snapshots__/bug-report-20260708T053736Z.snap.svg +2 -2
  22. package/tests/bug-reports/bug-report-20260708T055430Z/__snapshots__/bug-report-20260708T055430Z.snap.svg +2 -2
  23. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
  24. package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +2 -2
  25. package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +2 -2
  26. package/tests/bug-reports/bug-report-20260717T031704Z/__snapshots__/bug-report-20260717T031704Z.snap.svg +2 -2
  27. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
  28. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  29. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
  30. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +4 -4
  31. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +2 -2
  32. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  33. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +2 -2
  34. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +2 -2
  35. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
  36. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +2 -2
  37. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
  38. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
  39. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +2 -2
  40. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +2 -2
  41. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
  42. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
  43. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +2 -2
  44. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +2 -2
  45. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +851 -0
  46. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.json +2452 -0
  47. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.test.ts +12 -0
  48. package/tests/examples/__snapshots__/example01.snap.svg +2 -2
  49. package/tests/examples/__snapshots__/example02.snap.svg +12 -12
  50. package/tests/examples/__snapshots__/example03.snap.svg +2 -2
  51. package/tests/examples/__snapshots__/example04.snap.svg +2 -2
  52. package/tests/examples/__snapshots__/example05.snap.svg +2 -2
  53. package/tests/examples/__snapshots__/example06.snap.svg +2 -2
  54. package/tests/examples/__snapshots__/example07.snap.svg +2 -2
  55. package/tests/examples/__snapshots__/example08.snap.svg +2 -2
  56. package/tests/examples/__snapshots__/example09.snap.svg +2 -2
  57. package/tests/examples/__snapshots__/example10.snap.svg +2 -2
  58. package/tests/examples/__snapshots__/example11.snap.svg +2 -2
  59. package/tests/examples/__snapshots__/example12.snap.svg +2 -2
  60. package/tests/examples/__snapshots__/example13.snap.svg +2 -2
  61. package/tests/examples/__snapshots__/example14.snap.svg +2 -2
  62. package/tests/examples/__snapshots__/example15.snap.svg +2 -2
  63. package/tests/examples/__snapshots__/example16.snap.svg +2 -2
  64. package/tests/examples/__snapshots__/example17.snap.svg +2 -2
  65. package/tests/examples/__snapshots__/example18.snap.svg +2 -2
  66. package/tests/examples/__snapshots__/example19.snap.svg +2 -2
  67. package/tests/examples/__snapshots__/example20.snap.svg +2 -2
  68. package/tests/examples/__snapshots__/example21.snap.svg +2 -2
  69. package/tests/examples/__snapshots__/example22.snap.svg +2 -2
  70. package/tests/examples/__snapshots__/example23.snap.svg +2 -2
  71. package/tests/examples/__snapshots__/example24.snap.svg +2 -2
  72. package/tests/examples/__snapshots__/example25.snap.svg +2 -2
  73. package/tests/examples/__snapshots__/example26.snap.svg +2 -2
  74. package/tests/examples/__snapshots__/example27.snap.svg +2 -2
  75. package/tests/examples/__snapshots__/example28.snap.svg +2 -2
  76. package/tests/examples/__snapshots__/example29.snap.svg +2 -2
  77. package/tests/examples/__snapshots__/example30.snap.svg +2 -2
  78. package/tests/examples/__snapshots__/example31.snap.svg +2 -2
  79. package/tests/examples/__snapshots__/example32.snap.svg +2 -2
  80. package/tests/examples/__snapshots__/example33.snap.svg +2 -2
  81. package/tests/examples/__snapshots__/example34.snap.svg +2 -2
  82. package/tests/examples/__snapshots__/example35.snap.svg +2 -2
  83. package/tests/examples/__snapshots__/example36.snap.svg +2 -2
  84. package/tests/examples/__snapshots__/example37.snap.svg +2 -2
  85. package/tests/examples/__snapshots__/example38.snap.svg +2 -2
  86. package/tests/examples/__snapshots__/example39.snap.svg +2 -2
  87. package/tests/examples/__snapshots__/example40.snap.svg +2 -2
  88. package/tests/examples/__snapshots__/example41.snap.svg +2 -2
  89. package/tests/examples/__snapshots__/example42.snap.svg +2 -2
  90. package/tests/examples/__snapshots__/example43.snap.svg +2 -2
  91. package/tests/examples/__snapshots__/example44.snap.svg +2 -2
  92. package/tests/examples/__snapshots__/example45.snap.svg +2 -2
  93. package/tests/examples/__snapshots__/example46.snap.svg +2 -2
  94. package/tests/examples/__snapshots__/example47.snap.svg +2 -2
  95. package/tests/examples/__snapshots__/example48.snap.svg +2 -2
  96. package/tests/examples/__snapshots__/example49.snap.svg +2 -2
  97. package/tests/examples/__snapshots__/example50.snap.svg +2 -2
  98. package/tests/examples/__snapshots__/example51.snap.svg +4 -4
  99. package/tests/fixtures/matcher.ts +3 -3
  100. package/tests/fixtures/parallel-ground-rail.ts +53 -0
  101. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +4 -4
  102. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +12 -12
  103. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +5 -5
  104. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +2 -2
  105. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +2 -2
  106. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
  107. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +2 -2
  108. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +2 -2
  109. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +2 -2
  110. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
  111. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +2 -2
  112. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
  113. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
  114. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +2 -2
  115. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +2 -2
  116. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +2 -2
  117. package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +2 -2
  118. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
  119. package/tests/repros/__snapshots__/repro-ground-label-on-lower-rail-end.snap.svg +2 -2
  120. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
  121. package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +2 -2
  122. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +200 -0
  123. package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +2 -2
  124. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +2 -2
  125. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +2 -2
  126. package/tests/repros/__snapshots__/repro-pga300-c6-trace-alignment.snap.svg +2 -2
  127. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +288 -0
  128. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +4 -4
  129. package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +2 -2
  130. package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +2 -2
  131. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
  132. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +2 -2
  133. package/tests/repros/__snapshots__/repro-rp2040-v3v3-label-placement.snap.svg +2 -2
  134. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +2 -2
  135. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +4 -4
  136. package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +2 -2
  137. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +2 -2
  138. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
  139. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +4 -4
  140. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +2 -2
  141. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +2 -2
  142. package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
  143. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +2 -2
  144. package/tests/repros/__snapshots__/repro-vcc-pin1-detour.snap.svg +2 -2
  145. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +2 -2
  146. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
  147. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +4 -4
  148. package/tests/repros/__snapshots__/repro47-endpoint-obstacle-detour.snap.svg +2 -2
  149. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  150. package/tests/repros/__snapshots__/repro5-escape-padded-text-obstacles.snap.svg +2 -2
  151. package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +2 -2
  152. package/tests/repros/__snapshots__/repro84-stacked-gnd-label-orientation.snap.svg +2 -2
  153. package/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg +2 -2
  154. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +2 -2
  155. package/tests/repros/assets/repro-mspm0l1306-capacitor-symmetry.input.json +537 -0
  156. package/tests/repros/assets/repro-pga300-redundant-ground-traces.input.json +858 -0
  157. package/tests/repros/repro-mspm0l1306-capacitor-symmetry.test.ts +87 -0
  158. package/tests/repros/repro-pga300-redundant-ground-traces.test.ts +33 -0
  159. package/tests/solvers/InlineNetLabelSolver/__snapshots__/anchored-net-label-width.snap.svg +2 -2
  160. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-anchored-label-clearance.snap.svg +2 -2
  161. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-port-stub.snap.svg +2 -2
  162. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-two-pin-net.snap.svg +2 -2
  163. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label01.snap.svg +2 -2
  164. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +2 -2
  165. package/tests/solvers/InlineNetLabelSolver/__snapshots__/terminal-stub-trace-clearance.snap.svg +2 -2
  166. package/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts +105 -0
  167. package/tests/solvers/SameNetJunctionAlignmentSolver/ground-rail-label.test.ts +188 -0
  168. package/tests/solvers/SameNetJunctionAlignmentSolver/parallel-load-rails.test.ts +291 -0
  169. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +2 -2
  170. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_repro47-short-opposing-pins.snap.svg +2 -2
  171. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +2 -2
  172. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +2 -2
  173. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +2 -2
  174. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +2 -2
  175. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +2 -2
package/dist/index.d.ts CHANGED
@@ -202,6 +202,7 @@ declare class MspConnectionPairSolver extends BaseSolver {
202
202
  }>;
203
203
  userNetIdByPinId: Record<string, string | undefined>;
204
204
  directConnectionPinPairKeys: Set<string>;
205
+ private canRouteGroundPair;
205
206
  constructor({ inputProblem }: {
206
207
  inputProblem: InputProblem;
207
208
  });
package/dist/index.js CHANGED
@@ -363,6 +363,48 @@ var getConnectivityMapsFromInputProblem = (inputProblem) => {
363
363
  return { directConnMap, netConnMap };
364
364
  };
365
365
 
366
+ // lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts
367
+ import { ConnectivityMap as ConnectivityMap2 } from "connectivity-map";
368
+ var MAX_LOCAL_GROUND_BRANCH_OFFSET = 1;
369
+ var getGroundConnectionPolicy = (inputProblem) => {
370
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
371
+ const groundNetId = netConnMap.getNetConnectedToId("GND");
372
+ if (!groundNetId) return () => true;
373
+ const physicalConnMap = new ConnectivityMap2({});
374
+ for (const connection of inputProblem.directConnections) {
375
+ physicalConnMap.addConnections([connection.pinIds]);
376
+ }
377
+ const pins = new Map(
378
+ inputProblem.chips.flatMap(
379
+ (chip) => chip.pins.map((pin) => [pin.pinId, { pin, chip }])
380
+ )
381
+ );
382
+ const groundFacingPins = [...pins.values()].filter(
383
+ ({ pin, chip }) => chip.pins.length === 2 && netConnMap.getNetConnectedToId(pin.pinId) === groundNetId && (pin._facingDirection ?? getPinDirection(pin, chip)) === "y-"
384
+ );
385
+ const hasExplicitParallelGroundRail = groundFacingPins.some(({ pin: a }) => {
386
+ const group = physicalConnMap.getNetConnectedToId(a.pinId);
387
+ return group !== void 0 && groundFacingPins.some(
388
+ ({ pin: b }) => a.pinId !== b.pinId && Math.abs(a.y - b.y) < 1e-6 && Math.abs(a.x - b.x) > 1e-6 && physicalConnMap.getNetConnectedToId(b.pinId) === group
389
+ );
390
+ });
391
+ if (!hasExplicitParallelGroundRail) return () => true;
392
+ const isGroundFacingTerminal = (pinId) => {
393
+ const entry = pins.get(pinId);
394
+ return entry?.chip.pins.length === 2 && !physicalConnMap.getNetConnectedToId(pinId) && (entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) === "y-";
395
+ };
396
+ return (firstPinId, secondPinId) => {
397
+ if (!groundNetId || netConnMap.getNetConnectedToId(firstPinId) !== groundNetId || netConnMap.getNetConnectedToId(secondPinId) !== groundNetId) {
398
+ return true;
399
+ }
400
+ if (!isGroundFacingTerminal(firstPinId) && !isGroundFacingTerminal(secondPinId))
401
+ return true;
402
+ const first = pins.get(firstPinId)?.pin;
403
+ const second = pins.get(secondPinId)?.pin;
404
+ return !first || !second || Math.abs(first.y - second.y) <= MAX_LOCAL_GROUND_BRANCH_OFFSET + 1e-6;
405
+ };
406
+ };
407
+
366
408
  // lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts
367
409
  function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
368
410
  const n = pins.length;
@@ -512,9 +554,11 @@ var MspConnectionPairSolver = class extends BaseSolver {
512
554
  pinMap;
513
555
  userNetIdByPinId;
514
556
  directConnectionPinPairKeys;
557
+ canRouteGroundPair;
515
558
  constructor({ inputProblem }) {
516
559
  super();
517
560
  this.inputProblem = inputProblem;
561
+ this.canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
518
562
  this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
519
563
  const { directConnMap, netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
520
564
  this.dcConnMap = directConnMap;
@@ -571,6 +615,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
571
615
  const [pin1, pin2] = directlyConnectedPins;
572
616
  const p1 = this.pinMap[pin1];
573
617
  const p2 = this.pinMap[pin2];
618
+ if (!this.canRouteGroundPair(pin1, pin2)) return;
574
619
  const pinPairKey = getPinPairKey([pin1, pin2]);
575
620
  let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
576
621
  if (this.directConnectionPinPairKeys.has(pinPairKey)) {
@@ -615,7 +660,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
615
660
  directlyConnectedPins.map((p) => this.pinMap[p]).filter(Boolean),
616
661
  {
617
662
  maxDistance: this.maxMspPairDistance,
618
- forbidEdge: (a, b) => arePinsInDifferentSchematicSections(
663
+ forbidEdge: (a, b) => !this.canRouteGroundPair(a.pinId, b.pinId) || arePinsInDifferentSchematicSections(
619
664
  this.inputProblem,
620
665
  a,
621
666
  b
@@ -4827,6 +4872,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4827
4872
  super();
4828
4873
  this.params = params;
4829
4874
  const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
4875
+ const canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
4830
4876
  this.inputProblem = inputProblem;
4831
4877
  this.allSolvedTraces = [...alreadySolvedTraces];
4832
4878
  this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
@@ -4845,7 +4891,10 @@ var LongDistancePairSolver = class extends BaseSolver {
4845
4891
  }
4846
4892
  }
4847
4893
  this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
4848
- (connectionPair) => isLabeledPeripheralConnection({
4894
+ (connectionPair) => canRouteGroundPair(
4895
+ connectionPair.pins[0].pinId,
4896
+ connectionPair.pins[1].pinId
4897
+ ) && isLabeledPeripheralConnection({
4849
4898
  inputProblem: this.inputProblem,
4850
4899
  chipMap: this.chipMap,
4851
4900
  pins: connectionPair.pins
@@ -4865,6 +4914,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4865
4914
  const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
4866
4915
  const targetPin = pinMap.get(otherPinId);
4867
4916
  if (!targetPin) return [];
4917
+ if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return [];
4868
4918
  const isNamedTwoPinConnection = inputProblem.netConnections.some(
4869
4919
  (connection) => connection.pinIds.length === 2 && connection.pinIds.includes(sourcePin.pinId) && connection.pinIds.includes(targetPin.pinId)
4870
4920
  );
@@ -12322,6 +12372,9 @@ var UnroutedTraceRecoverySolver = class extends BaseSolver {
12322
12372
  }
12323
12373
  };
12324
12374
 
12375
+ // lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts
12376
+ import { getSegmentIntersection as getSegmentIntersection3 } from "@tscircuit/math-utils/line-intersections";
12377
+
12325
12378
  // lib/solvers/SameNetJunctionAlignmentSolver/pathIntersectsAnyNetLabel.ts
12326
12379
  var NET_LABEL_INTERIOR_INSET = 1e-6;
12327
12380
  var pathIntersectsAnyNetLabel = ({
@@ -12363,6 +12416,7 @@ var pathEntersAnyNetLabel = ({
12363
12416
  var MAX_ALIGNED_LOAD_PIN_OFFSET = 0.2;
12364
12417
  var MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2;
12365
12418
  var MAX_SHARED_PIN_RAIL_OFFSET = 0.05;
12419
+ var MIN_RETURN_STEM_LENGTH = 0.05;
12366
12420
  var getSharedPin = ({
12367
12421
  donorTrace,
12368
12422
  branchTrace
@@ -12400,6 +12454,7 @@ var railIsOnFacingSide = ({
12400
12454
  pin
12401
12455
  }) => {
12402
12456
  if (pin._facingDirection === "y+") return railY > pin.y;
12457
+ if (pin._facingDirection === "y-") return railY < pin.y;
12403
12458
  return false;
12404
12459
  };
12405
12460
  var railIsOnSharedPinFacingSide = ({
@@ -12532,6 +12587,27 @@ var candidateIsClear = ({
12532
12587
  if (doesPathCoincideWithTraces(candidateTrace.tracePath, otherNetTraces)) {
12533
12588
  return false;
12534
12589
  }
12590
+ for (const otherTrace of otherNetTraces) {
12591
+ const countIntersections = (path) => {
12592
+ const intersections = /* @__PURE__ */ new Set();
12593
+ for (let i = 1; i < path.length; i++) {
12594
+ for (let j = 1; j < otherTrace.tracePath.length; j++) {
12595
+ const point = getSegmentIntersection3(
12596
+ path[i - 1],
12597
+ path[i],
12598
+ otherTrace.tracePath[j - 1],
12599
+ otherTrace.tracePath[j]
12600
+ );
12601
+ if (point)
12602
+ intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
12603
+ }
12604
+ }
12605
+ return intersections.size;
12606
+ };
12607
+ if (countIntersections(candidateTrace.tracePath) > countIntersections(originalTrace.tracePath)) {
12608
+ return false;
12609
+ }
12610
+ }
12535
12611
  if (!attachedLabelIndexes.every(
12536
12612
  (index) => tracePathContainsPoint(
12537
12613
  candidateTrace.tracePath,
@@ -12569,6 +12645,57 @@ var candidateIsClear = ({
12569
12645
  if (!originalRail || !candidateRail) return false;
12570
12646
  return Math.abs(originalRail.start.y - candidateRail.start.y) <= MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET;
12571
12647
  };
12648
+ var getAlignedReturnBranchPath = ({
12649
+ donorTrace,
12650
+ branchTrace,
12651
+ returnStemScale = 1
12652
+ }) => {
12653
+ const sharedPin = getSharedPin({ donorTrace, branchTrace });
12654
+ if (!sharedPin) return null;
12655
+ const railPin = getOtherPin({ trace: donorTrace, sharedPin });
12656
+ const returnPin = getOtherPin({ trace: branchTrace, sharedPin });
12657
+ if (!railPin || !returnPin) return null;
12658
+ const facing = sharedPin._facingDirection;
12659
+ if (facing !== "y+" && facing !== "y-") return null;
12660
+ if (railPin._facingDirection !== facing || returnPin._facingDirection !== facing)
12661
+ return null;
12662
+ if (!nearlyEqual(sharedPin.y, railPin.y)) return null;
12663
+ const donorPath = simplifyPath(donorTrace.tracePath);
12664
+ if (donorPath.length !== 4) return null;
12665
+ const rail = getLongestHorizontalSegment(donorTrace);
12666
+ if (!rail || !railIsOnFacingSide({ railY: rail.start.y, pin: sharedPin }))
12667
+ return null;
12668
+ if (!isVertical2(donorPath[0], donorPath[1]) || !isVertical2(donorPath[2], donorPath[3]))
12669
+ return null;
12670
+ const sharedFirst = branchTrace.pins[0].pinId === sharedPin.pinId;
12671
+ const path = simplifyPath(
12672
+ sharedFirst ? branchTrace.tracePath : [...branchTrace.tracePath].reverse()
12673
+ );
12674
+ if (path.length !== 6) return null;
12675
+ if (!isVertical2(path[0], path[1]) || !isHorizontal2(path[1], path[2]) || !isVertical2(path[2], path[3]) || !isHorizontal2(path[3], path[4]) || !isVertical2(path[4], path[5]))
12676
+ return null;
12677
+ if (!railIsOnFacingSide({ railY: path[1].y, pin: sharedPin }) || !railIsOnFacingSide({ railY: path[4].y, pin: returnPin }))
12678
+ return null;
12679
+ const direction = facing === "y+" ? 1 : -1;
12680
+ if (direction * (returnPin.y - rail.start.y) <= 0) return null;
12681
+ const minX = Math.min(sharedPin.x, railPin.x);
12682
+ const maxX = Math.max(sharedPin.x, railPin.x);
12683
+ if (path[2].x < minX || path[2].x > maxX) return null;
12684
+ if (nearlyEqual(path[2].x, railPin.x) && nearlyEqual(path[1].y, rail.start.y))
12685
+ return null;
12686
+ const returnRailY = returnPin.y + (path[4].y - returnPin.y) * returnStemScale;
12687
+ if (Math.abs(returnRailY - returnPin.y) < MIN_RETURN_STEM_LENGTH - 1e-6)
12688
+ return null;
12689
+ const candidate = simplifyPath([
12690
+ { x: sharedPin.x, y: sharedPin.y },
12691
+ { x: sharedPin.x, y: rail.start.y },
12692
+ { x: railPin.x, y: rail.start.y },
12693
+ { x: railPin.x, y: returnRailY },
12694
+ { x: returnPin.x, y: returnRailY },
12695
+ { x: returnPin.x, y: returnPin.y }
12696
+ ]);
12697
+ return sharedFirst ? candidate : candidate.reverse();
12698
+ };
12572
12699
  var alignSameNetJunctions = ({
12573
12700
  inputProblem,
12574
12701
  traces,
@@ -12576,63 +12703,74 @@ var alignSameNetJunctions = ({
12576
12703
  }) => {
12577
12704
  let outputTraces = [...traces];
12578
12705
  let outputNetLabelPlacements = [...netLabelPlacements];
12579
- const alignedBranchTraceIds = /* @__PURE__ */ new Set();
12580
12706
  let alignedJunctionCount = 0;
12581
- const donorTraceIds = traces.map((trace) => trace.mspPairId);
12582
- const pendingDonorTraceIds = new Set(donorTraceIds);
12583
- for (let donorIndex = 0; donorIndex < donorTraceIds.length; donorIndex++) {
12584
- const donorTraceId = donorTraceIds[donorIndex];
12585
- pendingDonorTraceIds.delete(donorTraceId);
12586
- const donorTrace = outputTraces.find(
12587
- (trace) => trace.mspPairId === donorTraceId
12588
- );
12589
- for (const branchTrace of outputTraces) {
12590
- if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue;
12591
- if (donorTrace.mspPairId === branchTrace.mspPairId) continue;
12592
- if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue;
12593
- const candidatePath = getAlignedBranchPath({ donorTrace, branchTrace });
12594
- if (!candidatePath) continue;
12595
- const candidateTrace = { ...branchTrace, tracePath: candidatePath };
12596
- const originalPair = [donorTrace, branchTrace];
12597
- const candidatePair = [donorTrace, candidateTrace];
12598
- const removesVisibleSegment = getVisibleTraceSegmentCount(candidatePair) < getVisibleTraceSegmentCount(originalPair);
12599
- const shortensVisibleTrace = getVisibleTraceLength(candidatePair) < getVisibleTraceLength(originalPair) && !nearlyEqual(
12600
- getVisibleTraceLength(candidatePair),
12601
- getVisibleTraceLength(originalPair)
12707
+ for (const alignReturnBranches of [false, true]) {
12708
+ const alignedBranchTraceIds = /* @__PURE__ */ new Set();
12709
+ const donorTraceIds = traces.map((trace) => trace.mspPairId);
12710
+ const pendingDonorTraceIds = new Set(donorTraceIds);
12711
+ for (let donorIndex = 0; donorIndex < donorTraceIds.length; donorIndex++) {
12712
+ const donorTraceId = donorTraceIds[donorIndex];
12713
+ pendingDonorTraceIds.delete(donorTraceId);
12714
+ const donorTrace = outputTraces.find(
12715
+ (trace) => trace.mspPairId === donorTraceId
12602
12716
  );
12603
- if (!removesVisibleSegment && !shortensVisibleTrace) {
12604
- continue;
12605
- }
12606
- const attachedLabelIndexes = getAttachedLabelIndexes(
12607
- branchTrace,
12608
- outputNetLabelPlacements
12609
- );
12610
- const candidateNetLabelPlacements = moveAttachedLabels({
12611
- trace: branchTrace,
12612
- reroutedTracePath: candidatePath,
12613
- netLabelPlacements: outputNetLabelPlacements,
12614
- attachedLabelIndexes
12615
- });
12616
- if (!candidateIsClear({
12617
- candidateTrace,
12618
- originalTrace: branchTrace,
12619
- traces: outputTraces,
12620
- inputProblem,
12621
- candidateNetLabelPlacements,
12622
- attachedLabelIndexes
12623
- })) {
12624
- continue;
12625
- }
12626
- outputTraces = outputTraces.map((trace) => {
12627
- if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace;
12628
- return trace;
12629
- });
12630
- outputNetLabelPlacements = candidateNetLabelPlacements;
12631
- alignedBranchTraceIds.add(branchTrace.mspPairId);
12632
- alignedJunctionCount++;
12633
- if (!pendingDonorTraceIds.has(branchTrace.mspPairId)) {
12634
- donorTraceIds.push(branchTrace.mspPairId);
12635
- pendingDonorTraceIds.add(branchTrace.mspPairId);
12717
+ for (const branchTrace of outputTraces) {
12718
+ if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue;
12719
+ if (donorTrace.mspPairId === branchTrace.mspPairId) continue;
12720
+ if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue;
12721
+ const candidatePaths = alignReturnBranches ? [1, 0.5, 0.25].map(
12722
+ (returnStemScale) => getAlignedReturnBranchPath({
12723
+ donorTrace,
12724
+ branchTrace,
12725
+ returnStemScale
12726
+ })
12727
+ ) : [getAlignedBranchPath({ donorTrace, branchTrace })];
12728
+ for (const candidatePath of candidatePaths) {
12729
+ if (!candidatePath) continue;
12730
+ const candidateTrace = { ...branchTrace, tracePath: candidatePath };
12731
+ const originalPair = [donorTrace, branchTrace];
12732
+ const candidatePair = [donorTrace, candidateTrace];
12733
+ const removesVisibleSegment = getVisibleTraceSegmentCount(candidatePair) < getVisibleTraceSegmentCount(originalPair);
12734
+ const shortensVisibleTrace = getVisibleTraceLength(candidatePair) < getVisibleTraceLength(originalPair) && !nearlyEqual(
12735
+ getVisibleTraceLength(candidatePair),
12736
+ getVisibleTraceLength(originalPair)
12737
+ );
12738
+ if (!removesVisibleSegment && !shortensVisibleTrace) {
12739
+ continue;
12740
+ }
12741
+ const attachedLabelIndexes = getAttachedLabelIndexes(
12742
+ branchTrace,
12743
+ outputNetLabelPlacements
12744
+ );
12745
+ const candidateNetLabelPlacements = moveAttachedLabels({
12746
+ trace: branchTrace,
12747
+ reroutedTracePath: candidatePath,
12748
+ netLabelPlacements: outputNetLabelPlacements,
12749
+ attachedLabelIndexes
12750
+ });
12751
+ if (!candidateIsClear({
12752
+ candidateTrace,
12753
+ originalTrace: branchTrace,
12754
+ traces: outputTraces,
12755
+ inputProblem,
12756
+ candidateNetLabelPlacements,
12757
+ attachedLabelIndexes
12758
+ })) {
12759
+ continue;
12760
+ }
12761
+ outputTraces = outputTraces.map((trace) => {
12762
+ if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace;
12763
+ return trace;
12764
+ });
12765
+ outputNetLabelPlacements = candidateNetLabelPlacements;
12766
+ alignedBranchTraceIds.add(branchTrace.mspPairId);
12767
+ alignedJunctionCount++;
12768
+ if (!pendingDonorTraceIds.has(branchTrace.mspPairId)) {
12769
+ donorTraceIds.push(branchTrace.mspPairId);
12770
+ pendingDonorTraceIds.add(branchTrace.mspPairId);
12771
+ }
12772
+ break;
12773
+ }
12636
12774
  }
12637
12775
  }
12638
12776
  }
@@ -12643,6 +12781,75 @@ var alignSameNetJunctions = ({
12643
12781
  };
12644
12782
  };
12645
12783
 
12784
+ // lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts
12785
+ var placeGroundRailLabelsAtOuterEnd = ({
12786
+ inputProblem,
12787
+ traces,
12788
+ netLabelPlacements
12789
+ }) => {
12790
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
12791
+ const groundNetId = netConnMap.getNetConnectedToId("GND");
12792
+ if (!groundNetId) return netLabelPlacements;
12793
+ const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]));
12794
+ const traceMap = Object.fromEntries(
12795
+ traces.map((trace) => [trace.mspPairId, trace])
12796
+ );
12797
+ const obstacles = getObstacleRects(inputProblem);
12798
+ const output = [...netLabelPlacements];
12799
+ for (const rail of traces) {
12800
+ if (rail.globalConnNetId !== groundNetId) continue;
12801
+ const [a, b] = rail.pins;
12802
+ if (a.chipId === b.chipId || !nearlyEqual(a.y, b.y) || ![a, b].every(
12803
+ (pin) => pin._facingDirection === "y-" && chipMap.get(pin.chipId)?.pins.length === 2
12804
+ ))
12805
+ continue;
12806
+ const path = simplifyPath(rail.tracePath);
12807
+ if (path.length !== 4 || !isVertical2(path[0], path[1]) || !isHorizontal2(path[1], path[2]) || !isVertical2(path[2], path[3]) || path[1].y >= a.y)
12808
+ continue;
12809
+ for (const feed of traces) {
12810
+ if (feed.globalConnNetId !== groundNetId) continue;
12811
+ const shared = feed.pins.find((pin) => rail.pinIds.includes(pin.pinId));
12812
+ const icPin = feed.pins.find(
12813
+ (pin) => !rail.pinIds.includes(pin.pinId) && (chipMap.get(pin.chipId)?.pins.length ?? 0) > 2
12814
+ );
12815
+ if (!shared || !icPin || !nearlyEqual(icPin.y, path[1].y)) continue;
12816
+ const outer = shared.pinId === a.pinId ? b : a;
12817
+ if (Math.abs(outer.x - icPin.x) <= Math.abs(shared.x - icPin.x)) continue;
12818
+ const anchorPoint = { x: outer.x, y: path[1].y };
12819
+ if (!tracePathContainsPoint(rail.tracePath, anchorPoint)) continue;
12820
+ for (let index = 0; index < output.length; index++) {
12821
+ const label = output[index];
12822
+ if (label.globalConnNetId !== groundNetId || label.orientation !== "y-" || !label.mspConnectionPairIds.some(
12823
+ (id) => id === feed.mspPairId || id === rail.mspPairId
12824
+ ) || !tracePathContainsPoint(feed.tracePath, label.anchorPoint) && !tracePathContainsPoint(rail.tracePath, label.anchorPoint))
12825
+ continue;
12826
+ const center = getCenterFromAnchor(
12827
+ anchorPoint,
12828
+ label.orientation,
12829
+ label.width,
12830
+ label.height
12831
+ );
12832
+ const bounds = getRectBounds(center, label.width, label.height);
12833
+ if (obstacles.some((obstacle) => rectsOverlap(bounds, obstacle)) || traceCrossesBoundsInterior(bounds, traceMap) || output.some(
12834
+ (other, otherIndex) => otherIndex !== index && rectsOverlap(
12835
+ bounds,
12836
+ getRectBounds(other.center, other.width, other.height)
12837
+ )
12838
+ ))
12839
+ continue;
12840
+ output[index] = {
12841
+ ...label,
12842
+ anchorPoint,
12843
+ center,
12844
+ mspConnectionPairIds: [rail.mspPairId],
12845
+ pinIds: [...rail.pinIds]
12846
+ };
12847
+ }
12848
+ }
12849
+ }
12850
+ return output;
12851
+ };
12852
+
12646
12853
  // lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
12647
12854
  var SameNetJunctionAlignmentSolver = class extends BaseSolver {
12648
12855
  input;
@@ -12657,7 +12864,11 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
12657
12864
  _step() {
12658
12865
  const result = alignSameNetJunctions(this.input);
12659
12866
  this.outputTraces = result.traces;
12660
- this.outputNetLabelPlacements = result.netLabelPlacements;
12867
+ this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
12868
+ inputProblem: this.input.inputProblem,
12869
+ traces: result.traces,
12870
+ netLabelPlacements: result.netLabelPlacements
12871
+ });
12661
12872
  this.stats.alignedJunctionCount = result.alignedJunctionCount;
12662
12873
  this.solved = true;
12663
12874
  }
@@ -1,23 +1,24 @@
1
- import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
2
1
  import type { Point } from "@tscircuit/math-utils"
2
+ import type { ConnectivityMap } from "connectivity-map"
3
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
3
4
  import {
4
5
  DEFAULT_MAX_MSP_PAIR_DISTANCE,
5
6
  type MspConnectionPair,
6
7
  } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
7
8
  import type {
8
- InputProblem,
9
+ InputChip,
9
10
  InputPin,
11
+ InputProblem,
10
12
  PinId,
11
- InputChip,
12
13
  } from "lib/types/InputProblem"
13
- import { BaseSolver } from "../BaseSolver/BaseSolver"
14
- import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
15
- import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
16
- import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
17
- import type { ConnectivityMap } from "connectivity-map"
18
14
  import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
19
15
  import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
16
+ import { BaseSolver } from "../BaseSolver/BaseSolver"
17
+ import { getGroundConnectionPolicy } from "../MspConnectionPairSolver/getGroundConnectionPolicy"
20
18
  import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
19
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
20
+ import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
21
+ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
21
22
 
22
23
  const NEAREST_NEIGHBOR_COUNT = 3
23
24
 
@@ -163,6 +164,7 @@ export class LongDistancePairSolver extends BaseSolver {
163
164
 
164
165
  const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } =
165
166
  this.params
167
+ const canRouteGroundPair = getGroundConnectionPolicy(inputProblem)
166
168
 
167
169
  this.inputProblem = inputProblem
168
170
  this.allSolvedTraces = [...alreadySolvedTraces]
@@ -189,6 +191,10 @@ export class LongDistancePairSolver extends BaseSolver {
189
191
  // new nearest-neighbor candidates.
190
192
  this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
191
193
  (connectionPair) =>
194
+ canRouteGroundPair(
195
+ connectionPair.pins[0].pinId,
196
+ connectionPair.pins[1].pinId,
197
+ ) &&
192
198
  isLabeledPeripheralConnection({
193
199
  inputProblem: this.inputProblem,
194
200
  chipMap: this.chipMap,
@@ -219,6 +225,7 @@ export class LongDistancePairSolver extends BaseSolver {
219
225
  .flatMap((otherPinId) => {
220
226
  const targetPin = pinMap.get(otherPinId)
221
227
  if (!targetPin) return [] // Gracefully handle missing pins
228
+ if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return []
222
229
  const isNamedTwoPinConnection = inputProblem.netConnections.some(
223
230
  (connection) =>
224
231
  connection.pinIds.length === 2 &&
@@ -13,6 +13,7 @@ import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDiffer
13
13
  import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
14
14
  import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
15
15
  import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
16
+ import { getGroundConnectionPolicy } from "./getGroundConnectionPolicy"
16
17
  import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
17
18
  import { getLabeledConnectionRouteReason } from "./isLabeledPeripheralConnection"
18
19
 
@@ -45,11 +46,13 @@ export class MspConnectionPairSolver extends BaseSolver {
45
46
  pinMap: Record<string, InputPin & { chipId: string }>
46
47
  userNetIdByPinId: Record<string, string | undefined>
47
48
  directConnectionPinPairKeys: Set<string>
49
+ private canRouteGroundPair: (firstPinId: PinId, secondPinId: PinId) => boolean
48
50
 
49
51
  constructor({ inputProblem }: { inputProblem: InputProblem }) {
50
52
  super()
51
53
 
52
54
  this.inputProblem = inputProblem
55
+ this.canRouteGroundPair = getGroundConnectionPolicy(inputProblem)
53
56
  this.maxMspPairDistance =
54
57
  inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE
55
58
 
@@ -124,6 +127,7 @@ export class MspConnectionPairSolver extends BaseSolver {
124
127
  const [pin1, pin2] = directlyConnectedPins
125
128
  const p1 = this.pinMap[pin1!]!
126
129
  const p2 = this.pinMap[pin2!]!
130
+ if (!this.canRouteGroundPair(pin1!, pin2!)) return
127
131
  const pinPairKey = getPinPairKey([pin1!, pin2!])
128
132
  // Explicit source traces are classified by straight-line distance when
129
133
  // their input is created; named nets retain the orthogonal route metric.
@@ -195,6 +199,7 @@ export class MspConnectionPairSolver extends BaseSolver {
195
199
  {
196
200
  maxDistance: this.maxMspPairDistance,
197
201
  forbidEdge: (a, b) =>
202
+ !this.canRouteGroundPair(a.pinId, b.pinId) ||
198
203
  arePinsInDifferentSchematicSections(
199
204
  this.inputProblem,
200
205
  a as InputPin & { chipId: string },
@@ -0,0 +1,83 @@
1
+ import { ConnectivityMap } from "connectivity-map"
2
+ import type { InputProblem, PinId } from "lib/types/InputProblem"
3
+ import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
4
+ import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
5
+
6
+ // Keep nearby ground connections, but do not extend the usual 1 mm local
7
+ // routing range vertically just because a sheet allows long signal traces.
8
+ const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1
9
+
10
+ /** Avoid return wires between staggered, net-only ground-facing branches. */
11
+ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
12
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
13
+ const groundNetId = netConnMap.getNetConnectedToId("GND")
14
+ if (!groundNetId) return () => true
15
+ // Net identifiers do not constitute physical edges: two separate direct
16
+ // connections may have the same netId without requesting a wire between them.
17
+ const physicalConnMap = new ConnectivityMap({})
18
+ for (const connection of inputProblem.directConnections) {
19
+ physicalConnMap.addConnections([connection.pinIds])
20
+ }
21
+ const pins = new Map(
22
+ inputProblem.chips.flatMap((chip) =>
23
+ chip.pins.map((pin) => [pin.pinId, { pin, chip }] as const),
24
+ ),
25
+ )
26
+ const groundFacingPins = [...pins.values()].filter(
27
+ ({ pin, chip }) =>
28
+ chip.pins.length === 2 &&
29
+ netConnMap.getNetConnectedToId(pin.pinId) === groundNetId &&
30
+ (pin._facingDirection ?? getPinDirection(pin, chip)) === "y-",
31
+ )
32
+ // A deliberately wired, level bank of two-pin loads already has its own
33
+ // shared return rail. Do not extend that rail to independent lower branches
34
+ // just to reduce the number of GND symbols. Other ground topologies retain
35
+ // their existing routing behavior.
36
+ const hasExplicitParallelGroundRail = groundFacingPins.some(({ pin: a }) => {
37
+ const group = physicalConnMap.getNetConnectedToId(a.pinId)
38
+ return (
39
+ group !== undefined &&
40
+ groundFacingPins.some(
41
+ ({ pin: b }) =>
42
+ a.pinId !== b.pinId &&
43
+ Math.abs(a.y - b.y) < 1e-6 &&
44
+ Math.abs(a.x - b.x) > 1e-6 &&
45
+ physicalConnMap.getNetConnectedToId(b.pinId) === group,
46
+ )
47
+ )
48
+ })
49
+ if (!hasExplicitParallelGroundRail) return () => true
50
+ const isGroundFacingTerminal = (pinId: PinId) => {
51
+ const entry = pins.get(pinId)
52
+ return (
53
+ entry?.chip.pins.length === 2 &&
54
+ !physicalConnMap.getNetConnectedToId(pinId) &&
55
+ (entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) ===
56
+ "y-"
57
+ )
58
+ }
59
+
60
+ return (firstPinId: PinId, secondPinId: PinId): boolean => {
61
+ if (
62
+ !groundNetId ||
63
+ netConnMap.getNetConnectedToId(firstPinId) !== groundNetId ||
64
+ netConnMap.getNetConnectedToId(secondPinId) !== groundNetId
65
+ ) {
66
+ return true
67
+ }
68
+ if (
69
+ !isGroundFacingTerminal(firstPinId) &&
70
+ !isGroundFacingTerminal(secondPinId)
71
+ )
72
+ return true
73
+ const first = pins.get(firstPinId)?.pin
74
+ const second = pins.get(secondPinId)?.pin
75
+ // Level ground pins can still form a shared decoupling rail. A staggered
76
+ // terminal would need a return detour; use its local GND label instead.
77
+ return (
78
+ !first ||
79
+ !second ||
80
+ Math.abs(first.y - second.y) <= MAX_LOCAL_GROUND_BRANCH_OFFSET + 1e-6
81
+ )
82
+ }
83
+ }
@@ -6,6 +6,7 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
6
6
  import type { InputProblem } from "lib/types/InputProblem"
7
7
  import { getColorFromString } from "lib/utils/getColorFromString"
8
8
  import { alignSameNetJunctions } from "./alignSameNetJunctions"
9
+ import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
9
10
 
10
11
  interface SameNetJunctionAlignmentSolverInput {
11
12
  inputProblem: InputProblem
@@ -29,7 +30,11 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
29
30
  override _step() {
30
31
  const result = alignSameNetJunctions(this.input)
31
32
  this.outputTraces = result.traces
32
- this.outputNetLabelPlacements = result.netLabelPlacements
33
+ this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
34
+ inputProblem: this.input.inputProblem,
35
+ traces: result.traces,
36
+ netLabelPlacements: result.netLabelPlacements,
37
+ })
33
38
  this.stats.alignedJunctionCount = result.alignedJunctionCount
34
39
  this.solved = true
35
40
  }