@tscircuit/schematic-trace-solver 0.0.156 → 0.0.158

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 (176) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +151 -57
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +10 -2
  4. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +196 -67
  5. package/package.json +1 -1
  6. package/site/bug-reports/bug-report-20260826T072956Z.page.tsx +4 -0
  7. package/site/components/PipelineDebugger.tsx +4 -2
  8. package/site/examples/example51.page.tsx +3 -1
  9. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +2 -2
  10. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +2 -2
  11. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +2 -2
  12. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +2 -2
  13. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
  14. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +2 -2
  15. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +2 -2
  17. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +2 -2
  18. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  19. package/tests/bug-reports/bug-report-20260708T053736Z/__snapshots__/bug-report-20260708T053736Z.snap.svg +2 -2
  20. package/tests/bug-reports/bug-report-20260708T055430Z/__snapshots__/bug-report-20260708T055430Z.snap.svg +2 -2
  21. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
  22. package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +2 -2
  23. package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +2 -2
  24. package/tests/bug-reports/bug-report-20260717T031704Z/__snapshots__/bug-report-20260717T031704Z.snap.svg +2 -2
  25. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
  26. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  27. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
  28. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +4 -4
  29. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +2 -2
  30. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  31. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +2 -2
  32. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +2 -2
  33. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
  34. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +2 -2
  35. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
  36. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
  37. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +2 -2
  38. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +2 -2
  39. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
  40. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
  41. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +4 -4
  42. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +2 -2
  43. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +842 -0
  44. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.json +2452 -0
  45. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.test.ts +12 -0
  46. package/tests/examples/__snapshots__/example01.snap.svg +2 -2
  47. package/tests/examples/__snapshots__/example02.snap.svg +12 -12
  48. package/tests/examples/__snapshots__/example03.snap.svg +2 -2
  49. package/tests/examples/__snapshots__/example04.snap.svg +2 -2
  50. package/tests/examples/__snapshots__/example05.snap.svg +2 -2
  51. package/tests/examples/__snapshots__/example06.snap.svg +2 -2
  52. package/tests/examples/__snapshots__/example07.snap.svg +2 -2
  53. package/tests/examples/__snapshots__/example08.snap.svg +2 -2
  54. package/tests/examples/__snapshots__/example09.snap.svg +2 -2
  55. package/tests/examples/__snapshots__/example10.snap.svg +2 -2
  56. package/tests/examples/__snapshots__/example11.snap.svg +2 -2
  57. package/tests/examples/__snapshots__/example12.snap.svg +2 -2
  58. package/tests/examples/__snapshots__/example13.snap.svg +2 -2
  59. package/tests/examples/__snapshots__/example14.snap.svg +2 -2
  60. package/tests/examples/__snapshots__/example15.snap.svg +2 -2
  61. package/tests/examples/__snapshots__/example16.snap.svg +2 -2
  62. package/tests/examples/__snapshots__/example17.snap.svg +2 -2
  63. package/tests/examples/__snapshots__/example18.snap.svg +2 -2
  64. package/tests/examples/__snapshots__/example19.snap.svg +2 -2
  65. package/tests/examples/__snapshots__/example20.snap.svg +2 -2
  66. package/tests/examples/__snapshots__/example21.snap.svg +2 -2
  67. package/tests/examples/__snapshots__/example22.snap.svg +2 -2
  68. package/tests/examples/__snapshots__/example23.snap.svg +2 -2
  69. package/tests/examples/__snapshots__/example24.snap.svg +2 -2
  70. package/tests/examples/__snapshots__/example25.snap.svg +2 -2
  71. package/tests/examples/__snapshots__/example26.snap.svg +2 -2
  72. package/tests/examples/__snapshots__/example27.snap.svg +2 -2
  73. package/tests/examples/__snapshots__/example28.snap.svg +2 -2
  74. package/tests/examples/__snapshots__/example29.snap.svg +2 -2
  75. package/tests/examples/__snapshots__/example30.snap.svg +2 -2
  76. package/tests/examples/__snapshots__/example31.snap.svg +2 -2
  77. package/tests/examples/__snapshots__/example32.snap.svg +2 -2
  78. package/tests/examples/__snapshots__/example33.snap.svg +2 -2
  79. package/tests/examples/__snapshots__/example34.snap.svg +2 -2
  80. package/tests/examples/__snapshots__/example35.snap.svg +2 -2
  81. package/tests/examples/__snapshots__/example36.snap.svg +2 -2
  82. package/tests/examples/__snapshots__/example37.snap.svg +2 -2
  83. package/tests/examples/__snapshots__/example38.snap.svg +2 -2
  84. package/tests/examples/__snapshots__/example39.snap.svg +2 -2
  85. package/tests/examples/__snapshots__/example40.snap.svg +2 -2
  86. package/tests/examples/__snapshots__/example41.snap.svg +2 -2
  87. package/tests/examples/__snapshots__/example42.snap.svg +2 -2
  88. package/tests/examples/__snapshots__/example43.snap.svg +2 -2
  89. package/tests/examples/__snapshots__/example44.snap.svg +2 -2
  90. package/tests/examples/__snapshots__/example45.snap.svg +2 -2
  91. package/tests/examples/__snapshots__/example46.snap.svg +2 -2
  92. package/tests/examples/__snapshots__/example47.snap.svg +2 -2
  93. package/tests/examples/__snapshots__/example48.snap.svg +2 -2
  94. package/tests/examples/__snapshots__/example49.snap.svg +2 -2
  95. package/tests/examples/__snapshots__/example50.snap.svg +2 -2
  96. package/tests/examples/__snapshots__/example51.snap.svg +4 -4
  97. package/tests/fixtures/matcher.ts +3 -3
  98. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +73 -82
  99. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +12 -12
  100. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +5 -5
  101. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +2 -2
  102. package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +2 -2
  103. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
  104. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +2 -2
  105. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +2 -2
  106. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +2 -2
  107. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
  108. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +2 -2
  109. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
  110. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
  111. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +2 -2
  112. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +2 -2
  113. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +2 -2
  114. package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +129 -0
  115. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
  116. package/tests/repros/__snapshots__/repro-ground-label-on-lower-rail-end.snap.svg +2 -2
  117. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
  118. package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +2 -2
  119. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +182 -0
  120. package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +2 -2
  121. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +2 -2
  122. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +2 -2
  123. package/tests/repros/__snapshots__/repro-pga300-c6-trace-alignment.snap.svg +2 -2
  124. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +288 -0
  125. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +1290 -0
  126. package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +2 -2
  127. package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +2 -2
  128. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
  129. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +2 -2
  130. package/tests/repros/__snapshots__/repro-rp2040-v3v3-label-placement.snap.svg +2 -2
  131. package/tests/repros/__snapshots__/repro-rp2040-zero-crystal-fallback-netlabels.snap.svg +2 -2
  132. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +4 -4
  133. package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +130 -0
  134. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +2 -2
  135. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
  136. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +4 -4
  137. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +2 -2
  138. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +2 -2
  139. package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
  140. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +12 -12
  141. package/tests/repros/__snapshots__/repro-vcc-pin1-detour.snap.svg +2 -2
  142. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +2 -2
  143. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
  144. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +4 -4
  145. package/tests/repros/__snapshots__/repro47-endpoint-obstacle-detour.snap.svg +2 -2
  146. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  147. package/tests/repros/__snapshots__/repro5-escape-padded-text-obstacles.snap.svg +2 -2
  148. package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +2 -2
  149. package/tests/repros/__snapshots__/repro84-stacked-gnd-label-orientation.snap.svg +2 -2
  150. package/tests/repros/__snapshots__/small-variant-resistor-facing-direction.snap.svg +2 -2
  151. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +2 -2
  152. package/tests/repros/assets/repro-dc-power-isolated-routing.input.json +193 -0
  153. package/tests/repros/assets/repro-mspm0l1306-capacitor-symmetry.input.json +537 -0
  154. package/tests/repros/assets/repro-pga300-redundant-ground-traces.input.json +858 -0
  155. package/tests/repros/assets/repro-pmp11282-isolated-dcdc.input.json +4570 -0
  156. package/tests/repros/board-1273-trace-overlap-cycle.test.ts +1 -1
  157. package/tests/repros/repro-dc-power-isolated-routing.test.ts +13 -0
  158. package/tests/repros/repro-mspm0l1306-capacitor-symmetry.test.ts +54 -0
  159. package/tests/repros/repro-pga300-redundant-ground-traces.test.ts +33 -0
  160. package/tests/repros/repro-pmp11282-isolated-dcdc.test.ts +77 -0
  161. package/tests/repros/repro-tida00553-j4-cell-divider.test.ts +111 -0
  162. package/tests/solvers/InlineNetLabelSolver/__snapshots__/anchored-net-label-width.snap.svg +2 -2
  163. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-anchored-label-clearance.snap.svg +2 -2
  164. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-port-stub.snap.svg +2 -2
  165. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-two-pin-net.snap.svg +2 -2
  166. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label01.snap.svg +2 -2
  167. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +2 -2
  168. package/tests/solvers/InlineNetLabelSolver/__snapshots__/terminal-stub-trace-clearance.snap.svg +2 -2
  169. package/tests/solvers/SameNetJunctionAlignmentSolver/parallel-load-rails.test.ts +291 -0
  170. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +2 -2
  171. package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_repro47-short-opposing-pins.snap.svg +2 -2
  172. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +2 -2
  173. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +2 -2
  174. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +2 -2
  175. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +2 -2
  176. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +2 -2
package/dist/index.d.ts CHANGED
@@ -191,6 +191,7 @@ type MspConnectionPair = {
191
191
  declare class MspConnectionPairSolver extends BaseSolver {
192
192
  inputProblem: InputProblem;
193
193
  mspConnectionPairs: MspConnectionPair[];
194
+ mspConnectionPairIds: Set<string>;
194
195
  dcConnMap: ConnectivityMap;
195
196
  globalConnMap: ConnectivityMap;
196
197
  queuedDcNetIds: string[];
@@ -205,6 +206,7 @@ declare class MspConnectionPairSolver extends BaseSolver {
205
206
  inputProblem: InputProblem;
206
207
  });
207
208
  getConstructorParams(): ConstructorParameters<typeof MspConnectionPairSolver>[0];
209
+ private addMspConnectionPair;
208
210
  _step(): void;
209
211
  visualize(): GraphicsObject;
210
212
  }
package/dist/index.js CHANGED
@@ -503,6 +503,7 @@ var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
503
503
  var MspConnectionPairSolver = class extends BaseSolver {
504
504
  inputProblem;
505
505
  mspConnectionPairs = [];
506
+ mspConnectionPairIds = /* @__PURE__ */ new Set();
506
507
  dcConnMap;
507
508
  globalConnMap;
508
509
  queuedDcNetIds;
@@ -550,6 +551,11 @@ var MspConnectionPairSolver = class extends BaseSolver {
550
551
  inputProblem: this.inputProblem
551
552
  };
552
553
  }
554
+ addMspConnectionPair(pair) {
555
+ if (this.mspConnectionPairIds.has(pair.mspPairId)) return;
556
+ this.mspConnectionPairIds.add(pair.mspPairId);
557
+ this.mspConnectionPairs.push(pair);
558
+ }
553
559
  _step() {
554
560
  if (this.queuedDcNetIds.length === 0) {
555
561
  this.solved = true;
@@ -594,7 +600,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
594
600
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
595
601
  const userNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
596
602
  const suppressNetLabel = pairDistance > this.maxMspPairDistance && labeledConnectionRouteReason === "overlapping-fallback-labels";
597
- this.mspConnectionPairs.push({
603
+ this.addMspConnectionPair({
598
604
  mspPairId: `${pin1}-${pin2}`,
599
605
  dcConnNetId: dcNetId,
600
606
  globalConnNetId,
@@ -639,7 +645,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
639
645
  }
640
646
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
641
647
  const userNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
642
- this.mspConnectionPairs.push({
648
+ this.addMspConnectionPair({
643
649
  mspPairId: `${pin1}-${pin2}`,
644
650
  dcConnNetId: dcNetId,
645
651
  globalConnNetId,
@@ -12316,6 +12322,9 @@ var UnroutedTraceRecoverySolver = class extends BaseSolver {
12316
12322
  }
12317
12323
  };
12318
12324
 
12325
+ // lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts
12326
+ import { getSegmentIntersection as getSegmentIntersection3 } from "@tscircuit/math-utils/line-intersections";
12327
+
12319
12328
  // lib/solvers/SameNetJunctionAlignmentSolver/pathIntersectsAnyNetLabel.ts
12320
12329
  var NET_LABEL_INTERIOR_INSET = 1e-6;
12321
12330
  var pathIntersectsAnyNetLabel = ({
@@ -12357,6 +12366,7 @@ var pathEntersAnyNetLabel = ({
12357
12366
  var MAX_ALIGNED_LOAD_PIN_OFFSET = 0.2;
12358
12367
  var MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2;
12359
12368
  var MAX_SHARED_PIN_RAIL_OFFSET = 0.05;
12369
+ var MIN_RETURN_STEM_LENGTH = 0.05;
12360
12370
  var getSharedPin = ({
12361
12371
  donorTrace,
12362
12372
  branchTrace
@@ -12394,6 +12404,7 @@ var railIsOnFacingSide = ({
12394
12404
  pin
12395
12405
  }) => {
12396
12406
  if (pin._facingDirection === "y+") return railY > pin.y;
12407
+ if (pin._facingDirection === "y-") return railY < pin.y;
12397
12408
  return false;
12398
12409
  };
12399
12410
  var railIsOnSharedPinFacingSide = ({
@@ -12526,6 +12537,27 @@ var candidateIsClear = ({
12526
12537
  if (doesPathCoincideWithTraces(candidateTrace.tracePath, otherNetTraces)) {
12527
12538
  return false;
12528
12539
  }
12540
+ for (const otherTrace of otherNetTraces) {
12541
+ const countIntersections = (path) => {
12542
+ const intersections = /* @__PURE__ */ new Set();
12543
+ for (let i = 1; i < path.length; i++) {
12544
+ for (let j = 1; j < otherTrace.tracePath.length; j++) {
12545
+ const point = getSegmentIntersection3(
12546
+ path[i - 1],
12547
+ path[i],
12548
+ otherTrace.tracePath[j - 1],
12549
+ otherTrace.tracePath[j]
12550
+ );
12551
+ if (point)
12552
+ intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
12553
+ }
12554
+ }
12555
+ return intersections.size;
12556
+ };
12557
+ if (countIntersections(candidateTrace.tracePath) > countIntersections(originalTrace.tracePath)) {
12558
+ return false;
12559
+ }
12560
+ }
12529
12561
  if (!attachedLabelIndexes.every(
12530
12562
  (index) => tracePathContainsPoint(
12531
12563
  candidateTrace.tracePath,
@@ -12563,6 +12595,57 @@ var candidateIsClear = ({
12563
12595
  if (!originalRail || !candidateRail) return false;
12564
12596
  return Math.abs(originalRail.start.y - candidateRail.start.y) <= MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET;
12565
12597
  };
12598
+ var getAlignedReturnBranchPath = ({
12599
+ donorTrace,
12600
+ branchTrace,
12601
+ returnStemScale = 1
12602
+ }) => {
12603
+ const sharedPin = getSharedPin({ donorTrace, branchTrace });
12604
+ if (!sharedPin) return null;
12605
+ const railPin = getOtherPin({ trace: donorTrace, sharedPin });
12606
+ const returnPin = getOtherPin({ trace: branchTrace, sharedPin });
12607
+ if (!railPin || !returnPin) return null;
12608
+ const facing = sharedPin._facingDirection;
12609
+ if (facing !== "y+" && facing !== "y-") return null;
12610
+ if (railPin._facingDirection !== facing || returnPin._facingDirection !== facing)
12611
+ return null;
12612
+ if (!nearlyEqual(sharedPin.y, railPin.y)) return null;
12613
+ const donorPath = simplifyPath(donorTrace.tracePath);
12614
+ if (donorPath.length !== 4) return null;
12615
+ const rail = getLongestHorizontalSegment(donorTrace);
12616
+ if (!rail || !railIsOnFacingSide({ railY: rail.start.y, pin: sharedPin }))
12617
+ return null;
12618
+ if (!isVertical2(donorPath[0], donorPath[1]) || !isVertical2(donorPath[2], donorPath[3]))
12619
+ return null;
12620
+ const sharedFirst = branchTrace.pins[0].pinId === sharedPin.pinId;
12621
+ const path = simplifyPath(
12622
+ sharedFirst ? branchTrace.tracePath : [...branchTrace.tracePath].reverse()
12623
+ );
12624
+ if (path.length !== 6) return null;
12625
+ 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]))
12626
+ return null;
12627
+ if (!railIsOnFacingSide({ railY: path[1].y, pin: sharedPin }) || !railIsOnFacingSide({ railY: path[4].y, pin: returnPin }))
12628
+ return null;
12629
+ const direction = facing === "y+" ? 1 : -1;
12630
+ if (direction * (returnPin.y - rail.start.y) <= 0) return null;
12631
+ const minX = Math.min(sharedPin.x, railPin.x);
12632
+ const maxX = Math.max(sharedPin.x, railPin.x);
12633
+ if (path[2].x < minX || path[2].x > maxX) return null;
12634
+ if (nearlyEqual(path[2].x, railPin.x) && nearlyEqual(path[1].y, rail.start.y))
12635
+ return null;
12636
+ const returnRailY = returnPin.y + (path[4].y - returnPin.y) * returnStemScale;
12637
+ if (Math.abs(returnRailY - returnPin.y) < MIN_RETURN_STEM_LENGTH - 1e-6)
12638
+ return null;
12639
+ const candidate = simplifyPath([
12640
+ { x: sharedPin.x, y: sharedPin.y },
12641
+ { x: sharedPin.x, y: rail.start.y },
12642
+ { x: railPin.x, y: rail.start.y },
12643
+ { x: railPin.x, y: returnRailY },
12644
+ { x: returnPin.x, y: returnRailY },
12645
+ { x: returnPin.x, y: returnPin.y }
12646
+ ]);
12647
+ return sharedFirst ? candidate : candidate.reverse();
12648
+ };
12566
12649
  var alignSameNetJunctions = ({
12567
12650
  inputProblem,
12568
12651
  traces,
@@ -12570,63 +12653,74 @@ var alignSameNetJunctions = ({
12570
12653
  }) => {
12571
12654
  let outputTraces = [...traces];
12572
12655
  let outputNetLabelPlacements = [...netLabelPlacements];
12573
- const alignedBranchTraceIds = /* @__PURE__ */ new Set();
12574
12656
  let alignedJunctionCount = 0;
12575
- const donorTraceIds = traces.map((trace) => trace.mspPairId);
12576
- const pendingDonorTraceIds = new Set(donorTraceIds);
12577
- for (let donorIndex = 0; donorIndex < donorTraceIds.length; donorIndex++) {
12578
- const donorTraceId = donorTraceIds[donorIndex];
12579
- pendingDonorTraceIds.delete(donorTraceId);
12580
- const donorTrace = outputTraces.find(
12581
- (trace) => trace.mspPairId === donorTraceId
12582
- );
12583
- for (const branchTrace of outputTraces) {
12584
- if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue;
12585
- if (donorTrace.mspPairId === branchTrace.mspPairId) continue;
12586
- if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue;
12587
- const candidatePath = getAlignedBranchPath({ donorTrace, branchTrace });
12588
- if (!candidatePath) continue;
12589
- const candidateTrace = { ...branchTrace, tracePath: candidatePath };
12590
- const originalPair = [donorTrace, branchTrace];
12591
- const candidatePair = [donorTrace, candidateTrace];
12592
- const removesVisibleSegment = getVisibleTraceSegmentCount(candidatePair) < getVisibleTraceSegmentCount(originalPair);
12593
- const shortensVisibleTrace = getVisibleTraceLength(candidatePair) < getVisibleTraceLength(originalPair) && !nearlyEqual(
12594
- getVisibleTraceLength(candidatePair),
12595
- getVisibleTraceLength(originalPair)
12596
- );
12597
- if (!removesVisibleSegment && !shortensVisibleTrace) {
12598
- continue;
12599
- }
12600
- const attachedLabelIndexes = getAttachedLabelIndexes(
12601
- branchTrace,
12602
- outputNetLabelPlacements
12657
+ for (const alignReturnBranches of [false, true]) {
12658
+ const alignedBranchTraceIds = /* @__PURE__ */ new Set();
12659
+ const donorTraceIds = traces.map((trace) => trace.mspPairId);
12660
+ const pendingDonorTraceIds = new Set(donorTraceIds);
12661
+ for (let donorIndex = 0; donorIndex < donorTraceIds.length; donorIndex++) {
12662
+ const donorTraceId = donorTraceIds[donorIndex];
12663
+ pendingDonorTraceIds.delete(donorTraceId);
12664
+ const donorTrace = outputTraces.find(
12665
+ (trace) => trace.mspPairId === donorTraceId
12603
12666
  );
12604
- const candidateNetLabelPlacements = moveAttachedLabels({
12605
- trace: branchTrace,
12606
- reroutedTracePath: candidatePath,
12607
- netLabelPlacements: outputNetLabelPlacements,
12608
- attachedLabelIndexes
12609
- });
12610
- if (!candidateIsClear({
12611
- candidateTrace,
12612
- originalTrace: branchTrace,
12613
- traces: outputTraces,
12614
- inputProblem,
12615
- candidateNetLabelPlacements,
12616
- attachedLabelIndexes
12617
- })) {
12618
- continue;
12619
- }
12620
- outputTraces = outputTraces.map((trace) => {
12621
- if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace;
12622
- return trace;
12623
- });
12624
- outputNetLabelPlacements = candidateNetLabelPlacements;
12625
- alignedBranchTraceIds.add(branchTrace.mspPairId);
12626
- alignedJunctionCount++;
12627
- if (!pendingDonorTraceIds.has(branchTrace.mspPairId)) {
12628
- donorTraceIds.push(branchTrace.mspPairId);
12629
- pendingDonorTraceIds.add(branchTrace.mspPairId);
12667
+ for (const branchTrace of outputTraces) {
12668
+ if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue;
12669
+ if (donorTrace.mspPairId === branchTrace.mspPairId) continue;
12670
+ if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue;
12671
+ const candidatePaths = alignReturnBranches ? [1, 0.5, 0.25].map(
12672
+ (returnStemScale) => getAlignedReturnBranchPath({
12673
+ donorTrace,
12674
+ branchTrace,
12675
+ returnStemScale
12676
+ })
12677
+ ) : [getAlignedBranchPath({ donorTrace, branchTrace })];
12678
+ for (const candidatePath of candidatePaths) {
12679
+ if (!candidatePath) continue;
12680
+ const candidateTrace = { ...branchTrace, tracePath: candidatePath };
12681
+ const originalPair = [donorTrace, branchTrace];
12682
+ const candidatePair = [donorTrace, candidateTrace];
12683
+ const removesVisibleSegment = getVisibleTraceSegmentCount(candidatePair) < getVisibleTraceSegmentCount(originalPair);
12684
+ const shortensVisibleTrace = getVisibleTraceLength(candidatePair) < getVisibleTraceLength(originalPair) && !nearlyEqual(
12685
+ getVisibleTraceLength(candidatePair),
12686
+ getVisibleTraceLength(originalPair)
12687
+ );
12688
+ if (!removesVisibleSegment && !shortensVisibleTrace) {
12689
+ continue;
12690
+ }
12691
+ const attachedLabelIndexes = getAttachedLabelIndexes(
12692
+ branchTrace,
12693
+ outputNetLabelPlacements
12694
+ );
12695
+ const candidateNetLabelPlacements = moveAttachedLabels({
12696
+ trace: branchTrace,
12697
+ reroutedTracePath: candidatePath,
12698
+ netLabelPlacements: outputNetLabelPlacements,
12699
+ attachedLabelIndexes
12700
+ });
12701
+ if (!candidateIsClear({
12702
+ candidateTrace,
12703
+ originalTrace: branchTrace,
12704
+ traces: outputTraces,
12705
+ inputProblem,
12706
+ candidateNetLabelPlacements,
12707
+ attachedLabelIndexes
12708
+ })) {
12709
+ continue;
12710
+ }
12711
+ outputTraces = outputTraces.map((trace) => {
12712
+ if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace;
12713
+ return trace;
12714
+ });
12715
+ outputNetLabelPlacements = candidateNetLabelPlacements;
12716
+ alignedBranchTraceIds.add(branchTrace.mspPairId);
12717
+ alignedJunctionCount++;
12718
+ if (!pendingDonorTraceIds.has(branchTrace.mspPairId)) {
12719
+ donorTraceIds.push(branchTrace.mspPairId);
12720
+ pendingDonorTraceIds.add(branchTrace.mspPairId);
12721
+ }
12722
+ break;
12723
+ }
12630
12724
  }
12631
12725
  }
12632
12726
  }
@@ -35,6 +35,7 @@ export class MspConnectionPairSolver extends BaseSolver {
35
35
  inputProblem: InputProblem
36
36
 
37
37
  mspConnectionPairs: MspConnectionPair[] = []
38
+ mspConnectionPairIds = new Set<MspConnectionPairId>()
38
39
  dcConnMap: ConnectivityMap
39
40
  globalConnMap: ConnectivityMap
40
41
  queuedDcNetIds: string[]
@@ -97,6 +98,13 @@ export class MspConnectionPairSolver extends BaseSolver {
97
98
  }
98
99
  }
99
100
 
101
+ private addMspConnectionPair(pair: MspConnectionPair) {
102
+ if (this.mspConnectionPairIds.has(pair.mspPairId)) return
103
+
104
+ this.mspConnectionPairIds.add(pair.mspPairId)
105
+ this.mspConnectionPairs.push(pair)
106
+ }
107
+
100
108
  override _step() {
101
109
  if (this.queuedDcNetIds.length === 0) {
102
110
  this.solved = true
@@ -164,7 +172,7 @@ export class MspConnectionPairSolver extends BaseSolver {
164
172
  pairDistance > this.maxMspPairDistance &&
165
173
  labeledConnectionRouteReason === "overlapping-fallback-labels"
166
174
 
167
- this.mspConnectionPairs.push({
175
+ this.addMspConnectionPair({
168
176
  mspPairId: `${pin1}-${pin2}`,
169
177
  dcConnNetId: dcNetId,
170
178
  globalConnNetId,
@@ -226,7 +234,7 @@ export class MspConnectionPairSolver extends BaseSolver {
226
234
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1!)!
227
235
  const userNetId =
228
236
  this.userNetIdByPinId[pin1!] ?? this.userNetIdByPinId[pin2!]
229
- this.mspConnectionPairs.push({
237
+ this.addMspConnectionPair({
230
238
  mspPairId: `${pin1}-${pin2}`,
231
239
  dcConnNetId: dcNetId,
232
240
  globalConnNetId,
@@ -1,4 +1,5 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
+ import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections"
2
3
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
4
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
5
  import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
@@ -10,6 +11,7 @@ import {
10
11
  getVisibleTraceLength,
11
12
  getVisibleTraceSegmentCount,
12
13
  isHorizontal,
14
+ isVertical,
13
15
  nearlyEqual,
14
16
  } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
15
17
  import type { InputPin, InputProblem } from "lib/types/InputProblem"
@@ -37,6 +39,7 @@ const MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2
37
39
  // A shared vertical pin can anchor an existing rail when the rail is only a
38
40
  // symbol-stem correction away from the pin itself.
39
41
  const MAX_SHARED_PIN_RAIL_OFFSET = 0.05
42
+ const MIN_RETURN_STEM_LENGTH = 0.05
40
43
 
41
44
  const getSharedPin = ({
42
45
  donorTrace,
@@ -96,6 +99,7 @@ const railIsOnFacingSide = ({
96
99
  pin: InputPin
97
100
  }) => {
98
101
  if (pin._facingDirection === "y+") return railY > pin.y
102
+ if (pin._facingDirection === "y-") return railY < pin.y
99
103
  return false
100
104
  }
101
105
 
@@ -287,6 +291,33 @@ const candidateIsClear = ({
287
291
  return false
288
292
  }
289
293
 
294
+ // Moving a junction must not introduce an extra crossing on another net,
295
+ // including single-segment traces and intersections at a bend.
296
+ for (const otherTrace of otherNetTraces) {
297
+ const countIntersections = (path: Point[]) => {
298
+ const intersections = new Set<string>()
299
+ for (let i = 1; i < path.length; i++) {
300
+ for (let j = 1; j < otherTrace.tracePath.length; j++) {
301
+ const point = getSegmentIntersection(
302
+ path[i - 1]!,
303
+ path[i]!,
304
+ otherTrace.tracePath[j - 1]!,
305
+ otherTrace.tracePath[j]!,
306
+ )
307
+ if (point)
308
+ intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`)
309
+ }
310
+ }
311
+ return intersections.size
312
+ }
313
+ if (
314
+ countIntersections(candidateTrace.tracePath) >
315
+ countIntersections(originalTrace.tracePath)
316
+ ) {
317
+ return false
318
+ }
319
+ }
320
+
290
321
  if (
291
322
  !attachedLabelIndexes.every((index) =>
292
323
  tracePathContainsPoint(
@@ -340,6 +371,87 @@ const candidateIsClear = ({
340
371
  )
341
372
  }
342
373
 
374
+ /** Extend the outer load's stem instead of adding a return trunk between loads. */
375
+ const getAlignedReturnBranchPath = ({
376
+ donorTrace,
377
+ branchTrace,
378
+ returnStemScale = 1,
379
+ }: {
380
+ donorTrace: SolvedTracePath
381
+ branchTrace: SolvedTracePath
382
+ returnStemScale?: number
383
+ }): Point[] | null => {
384
+ const sharedPin = getSharedPin({ donorTrace, branchTrace })
385
+ if (!sharedPin) return null
386
+ const railPin = getOtherPin({ trace: donorTrace, sharedPin })
387
+ const returnPin = getOtherPin({ trace: branchTrace, sharedPin })
388
+ if (!railPin || !returnPin) return null
389
+ const facing = sharedPin._facingDirection
390
+ if (facing !== "y+" && facing !== "y-") return null
391
+ if (
392
+ railPin._facingDirection !== facing ||
393
+ returnPin._facingDirection !== facing
394
+ )
395
+ return null
396
+ if (!nearlyEqual(sharedPin.y, railPin.y)) return null
397
+
398
+ const donorPath = simplifyPath(donorTrace.tracePath)
399
+ if (donorPath.length !== 4) return null
400
+ const rail = getLongestHorizontalSegment(donorTrace)
401
+ if (!rail || !railIsOnFacingSide({ railY: rail.start.y, pin: sharedPin }))
402
+ return null
403
+ if (
404
+ !isVertical(donorPath[0]!, donorPath[1]!) ||
405
+ !isVertical(donorPath[2]!, donorPath[3]!)
406
+ )
407
+ return null
408
+
409
+ const sharedFirst = branchTrace.pins[0].pinId === sharedPin.pinId
410
+ const path = simplifyPath(
411
+ sharedFirst ? branchTrace.tracePath : [...branchTrace.tracePath].reverse(),
412
+ )
413
+ // A return branch leaves the shared stem, detours beside the loads, and
414
+ // approaches the remote parallel pin from its facing side.
415
+ if (path.length !== 6) return null
416
+ if (
417
+ !isVertical(path[0]!, path[1]!) ||
418
+ !isHorizontal(path[1]!, path[2]!) ||
419
+ !isVertical(path[2]!, path[3]!) ||
420
+ !isHorizontal(path[3]!, path[4]!) ||
421
+ !isVertical(path[4]!, path[5]!)
422
+ )
423
+ return null
424
+ if (
425
+ !railIsOnFacingSide({ railY: path[1]!.y, pin: sharedPin }) ||
426
+ !railIsOnFacingSide({ railY: path[4]!.y, pin: returnPin })
427
+ )
428
+ return null
429
+ const direction = facing === "y+" ? 1 : -1
430
+ if (direction * (returnPin.y - rail.start.y) <= 0) return null
431
+
432
+ const minX = Math.min(sharedPin.x, railPin.x)
433
+ const maxX = Math.max(sharedPin.x, railPin.x)
434
+ if (path[2]!.x < minX || path[2]!.x > maxX) return null
435
+ if (
436
+ nearlyEqual(path[2]!.x, railPin.x) &&
437
+ nearlyEqual(path[1]!.y, rail.start.y)
438
+ )
439
+ return null
440
+
441
+ const returnRailY = returnPin.y + (path[4]!.y - returnPin.y) * returnStemScale
442
+ if (Math.abs(returnRailY - returnPin.y) < MIN_RETURN_STEM_LENGTH - 1e-6)
443
+ return null
444
+ const candidate = simplifyPath([
445
+ { x: sharedPin.x, y: sharedPin.y },
446
+ { x: sharedPin.x, y: rail.start.y },
447
+ { x: railPin.x, y: rail.start.y },
448
+ { x: railPin.x, y: returnRailY },
449
+ { x: returnPin.x, y: returnRailY },
450
+ { x: returnPin.x, y: returnPin.y },
451
+ ])
452
+ return sharedFirst ? candidate : candidate.reverse()
453
+ }
454
+
343
455
  export const alignSameNetJunctions = ({
344
456
  inputProblem,
345
457
  traces,
@@ -347,76 +459,93 @@ export const alignSameNetJunctions = ({
347
459
  }: AlignSameNetJunctionsInput) => {
348
460
  let outputTraces = [...traces]
349
461
  let outputNetLabelPlacements = [...netLabelPlacements]
350
- const alignedBranchTraceIds = new Set<string>()
351
462
  let alignedJunctionCount = 0
352
463
 
353
- // Reuse each aligned branch as the rail for the next load in the chain. An
354
- // aligned branch may already have had its donor turn, so queue it again when
355
- // its geometry changes.
356
- const donorTraceIds = traces.map((trace) => trace.mspPairId)
357
- const pendingDonorTraceIds = new Set(donorTraceIds)
358
- for (let donorIndex = 0; donorIndex < donorTraceIds.length; donorIndex++) {
359
- const donorTraceId = donorTraceIds[donorIndex]!
360
- pendingDonorTraceIds.delete(donorTraceId)
361
- const donorTrace = outputTraces.find(
362
- (trace) => trace.mspPairId === donorTraceId,
363
- )!
364
- for (const branchTrace of outputTraces) {
365
- if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue
366
- if (donorTrace.mspPairId === branchTrace.mspPairId) continue
367
- if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue
368
-
369
- const candidatePath = getAlignedBranchPath({ donorTrace, branchTrace })
370
- if (!candidatePath) continue
371
- const candidateTrace = { ...branchTrace, tracePath: candidatePath }
372
- const originalPair = [donorTrace, branchTrace]
373
- const candidatePair = [donorTrace, candidateTrace]
374
- const removesVisibleSegment =
375
- getVisibleTraceSegmentCount(candidatePair) <
376
- getVisibleTraceSegmentCount(originalPair)
377
- const shortensVisibleTrace =
378
- getVisibleTraceLength(candidatePair) <
379
- getVisibleTraceLength(originalPair) &&
380
- !nearlyEqual(
381
- getVisibleTraceLength(candidatePair),
382
- getVisibleTraceLength(originalPair),
383
- )
384
- if (!removesVisibleSegment && !shortensVisibleTrace) {
385
- continue
386
- }
387
- const attachedLabelIndexes = getAttachedLabelIndexes(
388
- branchTrace,
389
- outputNetLabelPlacements,
390
- )
391
- const candidateNetLabelPlacements = moveAttachedLabels({
392
- trace: branchTrace,
393
- reroutedTracePath: candidatePath,
394
- netLabelPlacements: outputNetLabelPlacements,
395
- attachedLabelIndexes,
396
- })
397
- if (
398
- !candidateIsClear({
399
- candidateTrace,
400
- originalTrace: branchTrace,
401
- traces: outputTraces,
402
- inputProblem,
403
- candidateNetLabelPlacements,
404
- attachedLabelIndexes,
405
- })
406
- ) {
407
- continue
408
- }
464
+ // First level the load rails, then attach return branches to those final
465
+ // rails. Doing this in one pass could attach a branch to a rail that moves later.
466
+ for (const alignReturnBranches of [false, true]) {
467
+ const alignedBranchTraceIds = new Set<string>()
468
+ // Reuse each aligned branch as the rail for the next load in the chain. An
469
+ // aligned branch may already have had its donor turn, so queue it again when
470
+ // its geometry changes.
471
+ const donorTraceIds = traces.map((trace) => trace.mspPairId)
472
+ const pendingDonorTraceIds = new Set(donorTraceIds)
473
+ for (let donorIndex = 0; donorIndex < donorTraceIds.length; donorIndex++) {
474
+ const donorTraceId = donorTraceIds[donorIndex]!
475
+ pendingDonorTraceIds.delete(donorTraceId)
476
+ const donorTrace = outputTraces.find(
477
+ (trace) => trace.mspPairId === donorTraceId,
478
+ )!
479
+ for (const branchTrace of outputTraces) {
480
+ if (alignedBranchTraceIds.has(branchTrace.mspPairId)) continue
481
+ if (donorTrace.mspPairId === branchTrace.mspPairId) continue
482
+ if (donorTrace.globalConnNetId !== branchTrace.globalConnNetId) continue
483
+
484
+ // Prefer the existing return stem. If extending the outer column
485
+ // meets another net, try shorter escapes without moving any pins.
486
+ const candidatePaths = alignReturnBranches
487
+ ? [1, 0.5, 0.25].map((returnStemScale) =>
488
+ getAlignedReturnBranchPath({
489
+ donorTrace,
490
+ branchTrace,
491
+ returnStemScale,
492
+ }),
493
+ )
494
+ : [getAlignedBranchPath({ donorTrace, branchTrace })]
495
+ for (const candidatePath of candidatePaths) {
496
+ if (!candidatePath) continue
497
+ const candidateTrace = { ...branchTrace, tracePath: candidatePath }
498
+ const originalPair = [donorTrace, branchTrace]
499
+ const candidatePair = [donorTrace, candidateTrace]
500
+ const removesVisibleSegment =
501
+ getVisibleTraceSegmentCount(candidatePair) <
502
+ getVisibleTraceSegmentCount(originalPair)
503
+ const shortensVisibleTrace =
504
+ getVisibleTraceLength(candidatePair) <
505
+ getVisibleTraceLength(originalPair) &&
506
+ !nearlyEqual(
507
+ getVisibleTraceLength(candidatePair),
508
+ getVisibleTraceLength(originalPair),
509
+ )
510
+ if (!removesVisibleSegment && !shortensVisibleTrace) {
511
+ continue
512
+ }
513
+ const attachedLabelIndexes = getAttachedLabelIndexes(
514
+ branchTrace,
515
+ outputNetLabelPlacements,
516
+ )
517
+ const candidateNetLabelPlacements = moveAttachedLabels({
518
+ trace: branchTrace,
519
+ reroutedTracePath: candidatePath,
520
+ netLabelPlacements: outputNetLabelPlacements,
521
+ attachedLabelIndexes,
522
+ })
523
+ if (
524
+ !candidateIsClear({
525
+ candidateTrace,
526
+ originalTrace: branchTrace,
527
+ traces: outputTraces,
528
+ inputProblem,
529
+ candidateNetLabelPlacements,
530
+ attachedLabelIndexes,
531
+ })
532
+ ) {
533
+ continue
534
+ }
409
535
 
410
- outputTraces = outputTraces.map((trace) => {
411
- if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace
412
- return trace
413
- })
414
- outputNetLabelPlacements = candidateNetLabelPlacements
415
- alignedBranchTraceIds.add(branchTrace.mspPairId)
416
- alignedJunctionCount++
417
- if (!pendingDonorTraceIds.has(branchTrace.mspPairId)) {
418
- donorTraceIds.push(branchTrace.mspPairId)
419
- pendingDonorTraceIds.add(branchTrace.mspPairId)
536
+ outputTraces = outputTraces.map((trace) => {
537
+ if (trace.mspPairId === branchTrace.mspPairId) return candidateTrace
538
+ return trace
539
+ })
540
+ outputNetLabelPlacements = candidateNetLabelPlacements
541
+ alignedBranchTraceIds.add(branchTrace.mspPairId)
542
+ alignedJunctionCount++
543
+ if (!pendingDonorTraceIds.has(branchTrace.mspPairId)) {
544
+ donorTraceIds.push(branchTrace.mspPairId)
545
+ pendingDonorTraceIds.add(branchTrace.mspPairId)
546
+ }
547
+ break
548
+ }
420
549
  }
421
550
  }
422
551
  }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.156",
8
+ "version": "0.0.158",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />