@tscircuit/schematic-trace-solver 0.0.187 → 0.0.188

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 (46) hide show
  1. package/dist/index.d.ts +40 -23
  2. package/dist/index.js +1030 -311
  3. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +611 -230
  4. package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +2 -6
  5. package/lib/solvers/InlineNetLabelSolver/getInlineLabelObstacles.ts +64 -0
  6. package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +407 -0
  7. package/lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions.ts +145 -0
  8. package/lib/solvers/InlineNetLabelSolver/isLabelRepresentedInline.ts +15 -0
  9. package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +61 -31
  10. package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +83 -14
  11. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +4 -3
  12. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +3 -0
  13. package/lib/types/InputProblem.ts +2 -2
  14. package/package.json +1 -1
  15. package/site/bug-reports/bug-report-20260907T110144Z.page.tsx +4 -0
  16. package/site/bug-reports/bug-report-20260907T145640Z.page.tsx +4 -0
  17. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +2 -2
  18. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +18 -18
  19. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
  20. package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +21 -0
  21. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +5 -38
  22. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +3 -9
  23. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +18 -30
  24. package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +33 -0
  25. package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +282 -0
  26. package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.json +1073 -0
  27. package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +12 -0
  28. package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +1107 -0
  29. package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.json +3563 -0
  30. package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.test.ts +117 -0
  31. package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +4 -4
  32. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
  33. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +20 -20
  34. package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +37 -55
  35. package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +44 -68
  36. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +3 -9
  37. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +50 -74
  38. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  39. package/tests/repros/repro-battery-management-bq24073.test.ts +13 -1
  40. package/tests/repros/repro-hub-usb-sheet.test.ts +25 -0
  41. package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +59 -13
  42. package/tests/solvers/InlineNetLabelSolver/local-trace-label-shifts.test.ts +296 -0
  43. package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +7 -4
  44. package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +2 -1
  45. package/tests/solvers/InlineNetLabelSolver/shared-direct-and-named-connection.test.ts +62 -0
  46. package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +9 -3
package/dist/index.js CHANGED
@@ -342,19 +342,19 @@ var doesPairCrossRestrictedCenterLines = (params) => {
342
342
  chipMap
343
343
  });
344
344
  if (restrictedCenterLines.size === 0) return false;
345
- const EPS17 = 1e-9;
345
+ const EPS18 = 1e-9;
346
346
  const crossesSegment = (a, b) => {
347
347
  for (const [, rcl] of restrictedCenterLines) {
348
348
  if (rcl.axes.has("x") && typeof rcl.x === "number") {
349
- if ((a.x - rcl.x) * (b.x - rcl.x) < -EPS17) return true;
349
+ if ((a.x - rcl.x) * (b.x - rcl.x) < -EPS18) return true;
350
350
  }
351
351
  if (rcl.axes.has("y") && typeof rcl.y === "number") {
352
- if ((a.y - rcl.y) * (b.y - rcl.y) < -EPS17) return true;
352
+ if ((a.y - rcl.y) * (b.y - rcl.y) < -EPS18) return true;
353
353
  }
354
354
  }
355
355
  return false;
356
356
  };
357
- if (Math.abs(p1.x - p2.x) < EPS17 || Math.abs(p1.y - p2.y) < EPS17) {
357
+ if (Math.abs(p1.x - p2.x) < EPS18 || Math.abs(p1.y - p2.y) < EPS18) {
358
358
  return crossesSegment({ x: p1.x, y: p1.y }, { x: p2.x, y: p2.y });
359
359
  }
360
360
  const elbowHV = { x: p2.x, y: p1.y };
@@ -1657,8 +1657,8 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1657
1657
  if (!collision) {
1658
1658
  const first = path[0];
1659
1659
  const last = path[path.length - 1];
1660
- const EPS17 = 1e-9;
1661
- const samePoint = (p, q) => Math.abs(p.x - q.x) < EPS17 && Math.abs(p.y - q.y) < EPS17;
1660
+ const EPS18 = 1e-9;
1661
+ const samePoint = (p, q) => Math.abs(p.x - q.x) < EPS18 && Math.abs(p.y - q.y) < EPS18;
1662
1662
  if (samePoint(first, { x: PA.x, y: PA.y }) && samePoint(last, { x: PB.x, y: PB.y }) && (!this.hasAmbiguousPinDirections || pathMatchesPinDirections({
1663
1663
  path,
1664
1664
  pin1: { ...PA, _facingDirection: directions[0] },
@@ -2013,13 +2013,13 @@ var applyJogToTerminalSegment = ({
2013
2013
  segmentIndex: si,
2014
2014
  offset,
2015
2015
  JOG_SIZE,
2016
- EPS: EPS17 = 1e-6
2016
+ EPS: EPS18 = 1e-6
2017
2017
  }) => {
2018
2018
  if (si !== 0 && si !== pts.length - 2) return;
2019
2019
  const start = pts[si];
2020
2020
  const end = pts[si + 1];
2021
- const isVertical6 = Math.abs(start.x - end.x) < EPS17;
2022
- const isHorizontal5 = Math.abs(start.y - end.y) < EPS17;
2021
+ const isVertical6 = Math.abs(start.x - end.x) < EPS18;
2022
+ const isHorizontal5 = Math.abs(start.y - end.y) < EPS18;
2023
2023
  if (!isVertical6 && !isHorizontal5) return;
2024
2024
  const segDir = isVertical6 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
2025
2025
  if (pts.length === 2) {
@@ -2453,7 +2453,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2453
2453
  return islands;
2454
2454
  }
2455
2455
  findNextOverlapIssue() {
2456
- const EPS17 = 2e-3;
2456
+ const EPS18 = 2e-3;
2457
2457
  const netIds = Object.keys(this.traceNetIslands);
2458
2458
  for (let i = 0; i < netIds.length; i++) {
2459
2459
  for (let j = i + 1; j < netIds.length; j++) {
@@ -2480,7 +2480,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2480
2480
  segmentBIndex,
2481
2481
  rangeOverlap
2482
2482
  }) => {
2483
- if (rangeOverlap > EPS17) {
2483
+ if (rangeOverlap > EPS18) {
2484
2484
  const keyA = `${pathAIndex}:${segmentAIndex}`;
2485
2485
  const keyB = `${pathBIndex}:${segmentBIndex}`;
2486
2486
  if (!seenA.has(keyA)) {
@@ -2499,7 +2499,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2499
2499
  }
2500
2500
  return;
2501
2501
  }
2502
- if (rangeOverlap < -EPS17) return;
2502
+ if (rangeOverlap < -EPS18) return;
2503
2503
  const pathA = pathsA[pathAIndex].tracePath;
2504
2504
  const pathB = pathsB[pathBIndex].tracePath;
2505
2505
  const segmentAStart = pathA[segmentAIndex];
@@ -2522,8 +2522,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2522
2522
  for (let sa = 0; sa < ptsA.length - 1; sa++) {
2523
2523
  const a1 = ptsA[sa];
2524
2524
  const a2 = ptsA[sa + 1];
2525
- const aVert = Math.abs(a1.x - a2.x) < EPS17;
2526
- const aHorz = Math.abs(a1.y - a2.y) < EPS17;
2525
+ const aVert = Math.abs(a1.x - a2.x) < EPS18;
2526
+ const aHorz = Math.abs(a1.y - a2.y) < EPS18;
2527
2527
  if (!aVert && !aHorz) continue;
2528
2528
  for (let pb = 0; pb < pathsB.length; pb++) {
2529
2529
  const pathB = pathsB[pb];
@@ -2534,11 +2534,11 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2534
2534
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
2535
2535
  const b1 = ptsB[sb];
2536
2536
  const b2 = ptsB[sb + 1];
2537
- const bVert = Math.abs(b1.x - b2.x) < EPS17;
2538
- const bHorz = Math.abs(b1.y - b2.y) < EPS17;
2537
+ const bVert = Math.abs(b1.x - b2.x) < EPS18;
2538
+ const bHorz = Math.abs(b1.y - b2.y) < EPS18;
2539
2539
  if (!bVert && !bHorz) continue;
2540
2540
  if (aVert && bVert) {
2541
- if (Math.abs(a1.x - b1.x) < EPS17) {
2541
+ if (Math.abs(a1.x - b1.x) < EPS18) {
2542
2542
  recordCollinearInteraction({
2543
2543
  pathAIndex: pa,
2544
2544
  segmentAIndex: sa,
@@ -2548,7 +2548,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2548
2548
  });
2549
2549
  }
2550
2550
  } else if (aHorz && bHorz) {
2551
- if (Math.abs(a1.y - b1.y) < EPS17) {
2551
+ if (Math.abs(a1.y - b1.y) < EPS18) {
2552
2552
  recordCollinearInteraction({
2553
2553
  pathAIndex: pa,
2554
2554
  segmentAIndex: sa,
@@ -2604,14 +2604,14 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2604
2604
  return null;
2605
2605
  }
2606
2606
  findNextDiagonalSegment() {
2607
- const EPS17 = 2e-3;
2607
+ const EPS18 = 2e-3;
2608
2608
  for (const mspPairId in this.correctedTraceMap) {
2609
2609
  const tracePath = this.correctedTraceMap[mspPairId].tracePath;
2610
2610
  for (let i = 0; i < tracePath.length - 1; i++) {
2611
2611
  const p1 = tracePath[i];
2612
2612
  const p2 = tracePath[i + 1];
2613
- const isHorizontal5 = Math.abs(p1.y - p2.y) < EPS17;
2614
- const isVertical6 = Math.abs(p1.x - p2.x) < EPS17;
2613
+ const isHorizontal5 = Math.abs(p1.y - p2.y) < EPS18;
2614
+ const isVertical6 = Math.abs(p1.x - p2.x) < EPS18;
2615
2615
  if (!isHorizontal5 && !isVertical6) {
2616
2616
  return { mspPairId, tracePath, i, p1, p2 };
2617
2617
  }
@@ -2625,13 +2625,13 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2625
2625
  return false;
2626
2626
  }
2627
2627
  const { mspPairId, tracePath, i, p1, p2 } = diagonalInfo;
2628
- const EPS17 = 2e-3;
2628
+ const EPS18 = 2e-3;
2629
2629
  const p0 = i > 0 ? tracePath[i - 1] : null;
2630
2630
  const p3 = i + 2 < tracePath.length ? tracePath[i + 2] : null;
2631
- const prevIsVertical = p0 ? Math.abs(p0.x - p1.x) < EPS17 : false;
2632
- const prevIsHorizontal = p0 ? Math.abs(p0.y - p1.y) < EPS17 : false;
2633
- const nextIsVertical = p3 ? Math.abs(p2.x - p3.x) < EPS17 : false;
2634
- const nextIsHorizontal = p3 ? Math.abs(p2.y - p3.y) < EPS17 : false;
2631
+ const prevIsVertical = p0 ? Math.abs(p0.x - p1.x) < EPS18 : false;
2632
+ const prevIsHorizontal = p0 ? Math.abs(p0.y - p1.y) < EPS18 : false;
2633
+ const nextIsVertical = p3 ? Math.abs(p2.x - p3.x) < EPS18 : false;
2634
+ const nextIsHorizontal = p3 ? Math.abs(p2.y - p3.y) < EPS18 : false;
2635
2635
  const elbow1 = { x: p1.x, y: p2.y };
2636
2636
  const elbow2 = { x: p2.x, y: p1.y };
2637
2637
  let score1 = 0;
@@ -2793,24 +2793,24 @@ var ChipObstacleSpatialIndex = class {
2793
2793
  };
2794
2794
 
2795
2795
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
2796
- function segmentIntersectsRect2(p1, p2, rect, EPS17 = 1e-9) {
2797
- const isVert = Math.abs(p1.x - p2.x) < EPS17;
2798
- const isHorz = Math.abs(p1.y - p2.y) < EPS17;
2796
+ function segmentIntersectsRect2(p1, p2, rect, EPS18 = 1e-9) {
2797
+ const isVert = Math.abs(p1.x - p2.x) < EPS18;
2798
+ const isHorz = Math.abs(p1.y - p2.y) < EPS18;
2799
2799
  if (!isVert && !isHorz) return false;
2800
2800
  if (isVert) {
2801
2801
  const x = p1.x;
2802
- if (x < rect.minX - EPS17 || x > rect.maxX + EPS17) return false;
2802
+ if (x < rect.minX - EPS18 || x > rect.maxX + EPS18) return false;
2803
2803
  const segMinY = Math.min(p1.y, p2.y);
2804
2804
  const segMaxY = Math.max(p1.y, p2.y);
2805
2805
  const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
2806
- return overlap > EPS17;
2806
+ return overlap > EPS18;
2807
2807
  } else {
2808
2808
  const y = p1.y;
2809
- if (y < rect.minY - EPS17 || y > rect.maxY + EPS17) return false;
2809
+ if (y < rect.minY - EPS18 || y > rect.maxY + EPS18) return false;
2810
2810
  const segMinX = Math.min(p1.x, p2.x);
2811
2811
  const segMaxX = Math.max(p1.x, p2.x);
2812
2812
  const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
2813
- return overlap > EPS17;
2813
+ return overlap > EPS18;
2814
2814
  }
2815
2815
  }
2816
2816
  function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
@@ -3209,14 +3209,14 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
3209
3209
  };
3210
3210
  let bestCandidate = null;
3211
3211
  let bestScore = -Infinity;
3212
- const EPS17 = 1e-6;
3212
+ const EPS18 = 1e-6;
3213
3213
  for (const curr of tracesToScan) {
3214
3214
  const pts = curr.tracePath.slice();
3215
3215
  for (let si = 0; si < pts.length - 1; si++) {
3216
3216
  const a = pts[si];
3217
3217
  const b = pts[si + 1];
3218
- const isH = Math.abs(a.y - b.y) < EPS17;
3219
- const isV = Math.abs(a.x - b.x) < EPS17;
3218
+ const isH = Math.abs(a.y - b.y) < EPS18;
3219
+ const isV = Math.abs(a.x - b.x) < EPS18;
3220
3220
  if (!isH && !isV) continue;
3221
3221
  const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
3222
3222
  const candidateOrients = orientations.filter(
@@ -5049,7 +5049,7 @@ var clipPathAtFirstIntersection = (tracePath, existingTraces) => {
5049
5049
  for (let pathIndex = 0; pathIndex < tracePath.length - 1; pathIndex++) {
5050
5050
  const pathStart = tracePath[pathIndex];
5051
5051
  const pathEnd = tracePath[pathIndex + 1];
5052
- const intersections = existingTraces.flatMap(
5052
+ const intersections2 = existingTraces.flatMap(
5053
5053
  (trace) => trace.tracePath.slice(0, -1).flatMap((traceStart, traceIndex) => {
5054
5054
  const intersection2 = getAxisAlignedIntersection(
5055
5055
  pathStart,
@@ -5060,10 +5060,10 @@ var clipPathAtFirstIntersection = (tracePath, existingTraces) => {
5060
5060
  return intersection2 ? [intersection2] : [];
5061
5061
  })
5062
5062
  );
5063
- intersections.sort(
5063
+ intersections2.sort(
5064
5064
  (first, second) => distance3(pathStart, first) - distance3(pathStart, second)
5065
5065
  );
5066
- const intersection = intersections[0];
5066
+ const intersection = intersections2[0];
5067
5067
  if (!intersection) continue;
5068
5068
  const clippedPath = tracePath.slice(0, pathIndex + 1);
5069
5069
  if (distance3(clippedPath.at(-1), intersection) > EPS6) {
@@ -6800,7 +6800,7 @@ var getTraceObstacles = (allTraces, excludeTraceId) => {
6800
6800
  import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections";
6801
6801
  var EPS9 = 1e-6;
6802
6802
  var findIntersectionsWithObstacles = (p1, p2, obstacles) => {
6803
- const intersections = [];
6803
+ const intersections2 = [];
6804
6804
  for (const obstacle of obstacles) {
6805
6805
  const obstaclePath = obstacle.points;
6806
6806
  for (let i = 0; i < obstaclePath.length - 1; i++) {
@@ -6811,11 +6811,11 @@ var findIntersectionsWithObstacles = (p1, p2, obstacles) => {
6811
6811
  }
6812
6812
  const intersection = getSegmentIntersection(p1, p2, o1, o2);
6813
6813
  if (intersection) {
6814
- intersections.push(intersection);
6814
+ intersections2.push(intersection);
6815
6815
  }
6816
6816
  }
6817
6817
  }
6818
- return intersections;
6818
+ return intersections2;
6819
6819
  };
6820
6820
  var isSamePoint = (first, second) => Math.abs(first.x - second.x) < EPS9 && Math.abs(first.y - second.y) < EPS9;
6821
6821
  var findPerpendicularPathCrossings = (path, otherPath, options = {}) => {
@@ -12400,6 +12400,22 @@ globalConnNetId: ${label.globalConnNetId}`
12400
12400
  }
12401
12401
  };
12402
12402
 
12403
+ // lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts
12404
+ var getAnchoredNetLabelRenderedBounds = (placement) => {
12405
+ if (placement.orientation !== "x-" && placement.orientation !== "x+") {
12406
+ return getRectBounds(placement.center, placement.width, placement.height);
12407
+ }
12408
+ const width = Math.max(placement.width, placement.height);
12409
+ const height = NET_LABEL_HORIZONTAL_HEIGHT;
12410
+ const center = getCenterFromAnchor(
12411
+ placement.anchorPoint,
12412
+ placement.orientation,
12413
+ width,
12414
+ height
12415
+ );
12416
+ return getRectBounds(center, width, height);
12417
+ };
12418
+
12403
12419
  // lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts
12404
12420
  var ANCHOR_TRACE_CLEARANCE = 1e-4;
12405
12421
  var SEGMENT_PARALLEL_EPS = 1e-6;
@@ -12440,6 +12456,19 @@ function sampleAnchorsAlongSegment(a, b) {
12440
12456
  return anchors;
12441
12457
  }
12442
12458
  var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12459
+ constructor(params) {
12460
+ super();
12461
+ this.params = params;
12462
+ this.inputProblem = params.inputProblem;
12463
+ this.traces = params.traces;
12464
+ this.netLabelPlacements = params.netLabelPlacements;
12465
+ this.outputNetLabelPlacements = [...params.netLabelPlacements];
12466
+ this.chipIndex = params.inputProblem._chipObstacleSpatialIndex ?? (params.inputProblem.chips.length > 0 ? new ChipObstacleSpatialIndex(params.inputProblem.chips) : void 0);
12467
+ this.traceMap = Object.fromEntries(
12468
+ params.traces.map((t) => [t.mspPairId, t])
12469
+ );
12470
+ }
12471
+ params;
12443
12472
  inputProblem;
12444
12473
  traces;
12445
12474
  netLabelPlacements;
@@ -12453,40 +12482,37 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12453
12482
  labelsToTry = [];
12454
12483
  candidateQueue = [];
12455
12484
  candidateIndex = 0;
12456
- constructor(params) {
12457
- super();
12458
- this.inputProblem = params.inputProblem;
12459
- this.traces = params.traces;
12460
- this.netLabelPlacements = params.netLabelPlacements;
12461
- this.outputNetLabelPlacements = [...params.netLabelPlacements];
12462
- this.chipIndex = params.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(params.inputProblem.chips);
12463
- this.traceMap = Object.fromEntries(
12464
- params.traces.map((t) => [t.mspPairId, t])
12465
- );
12466
- }
12467
12485
  getConstructorParams() {
12468
12486
  return {
12469
12487
  inputProblem: this.inputProblem,
12470
12488
  traces: this.traces,
12471
- netLabelPlacements: this.netLabelPlacements
12489
+ netLabelPlacements: this.netLabelPlacements,
12490
+ fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
12491
+ useRenderedLabelBounds: this.params.useRenderedLabelBounds
12472
12492
  };
12473
12493
  }
12474
12494
  getOutput() {
12475
12495
  return { netLabelPlacements: this.outputNetLabelPlacements };
12476
12496
  }
12477
12497
  labelBounds(label) {
12478
- return getRectBounds(label.center, label.width, label.height);
12498
+ if (!this.params.useRenderedLabelBounds || this.params.fixedNetLabelPlacements?.includes(label))
12499
+ return getRectBounds(label.center, label.width, label.height);
12500
+ return getAnchoredNetLabelRenderedBounds(label);
12479
12501
  }
12480
12502
  collisionKey(a, b) {
12481
12503
  return [a.globalConnNetId, b.globalConnNetId].sort().join("::");
12482
12504
  }
12483
12505
  findNextCollidingPair() {
12484
- const labels = this.outputNetLabelPlacements;
12485
- for (let i = 0; i < labels.length; i++) {
12506
+ const labels = [
12507
+ ...this.outputNetLabelPlacements,
12508
+ ...this.params.fixedNetLabelPlacements ?? []
12509
+ ];
12510
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
12486
12511
  for (let j = i + 1; j < labels.length; j++) {
12487
12512
  const a = labels[i];
12488
12513
  const b = labels[j];
12489
- if (a.globalConnNetId === b.globalConnNetId) continue;
12514
+ if (a.globalConnNetId === b.globalConnNetId && !this.params.fixedNetLabelPlacements?.includes(b))
12515
+ continue;
12490
12516
  if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue;
12491
12517
  if (boundsOverlap2(this.labelBounds(a), this.labelBounds(b)))
12492
12518
  return [a, b];
@@ -12526,7 +12552,14 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12526
12552
  center,
12527
12553
  width,
12528
12554
  height,
12529
- bounds: getRectBounds(center, width, height),
12555
+ bounds: this.params.useRenderedLabelBounds ? getAnchoredNetLabelRenderedBounds({
12556
+ ...label,
12557
+ orientation,
12558
+ anchorPoint: anchor,
12559
+ center,
12560
+ width,
12561
+ height
12562
+ }) : getRectBounds(center, width, height),
12530
12563
  hostPairId,
12531
12564
  hostSegIndex,
12532
12565
  status: null
@@ -12578,7 +12611,7 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12578
12611
  }
12579
12612
  checkCandidate(candidate, movingLabelNetId, obstacleLabels) {
12580
12613
  const { bounds, hostPairId, hostSegIndex } = candidate;
12581
- if (this.chipIndex.getChipsInBounds(bounds).length > 0)
12614
+ if ((this.chipIndex?.getChipsInBounds(bounds).length ?? 0) > 0)
12582
12615
  return "chip-collision";
12583
12616
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
12584
12617
  return "text-collision";
@@ -12586,7 +12619,8 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12586
12619
  return "trace-collision";
12587
12620
  }
12588
12621
  for (const obstacle of obstacleLabels) {
12589
- if (obstacle.globalConnNetId === movingLabelNetId) continue;
12622
+ if (obstacle.globalConnNetId === movingLabelNetId && !this.params.fixedNetLabelPlacements?.includes(obstacle))
12623
+ continue;
12590
12624
  if (boundsOverlap2(bounds, this.labelBounds(obstacle)))
12591
12625
  return "label-collision";
12592
12626
  }
@@ -12598,6 +12632,27 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12598
12632
  this.candidateIndex = 0;
12599
12633
  this.candidateResults = [];
12600
12634
  }
12635
+ /** Reuse the normal anchor search for a local trace-clearance proposal. */
12636
+ getNearbyValidPlacements(label, maxDistance) {
12637
+ const distance7 = (candidate) => Math.hypot(
12638
+ candidate.anchor.x - label.anchorPoint.x,
12639
+ candidate.anchor.y - label.anchorPoint.y
12640
+ );
12641
+ const obstacles = [
12642
+ ...this.outputNetLabelPlacements.filter((other) => other !== label),
12643
+ ...this.params.fixedNetLabelPlacements ?? []
12644
+ ];
12645
+ return this.buildCandidatesForLabel(label).filter(
12646
+ (candidate) => distance7(candidate) <= maxDistance && this.checkCandidate(candidate, label.globalConnNetId, obstacles) === "ok"
12647
+ ).sort((a, b) => distance7(a) - distance7(b)).map((candidate) => ({
12648
+ ...label,
12649
+ orientation: candidate.orientation,
12650
+ anchorPoint: candidate.anchor,
12651
+ center: candidate.center,
12652
+ width: candidate.width,
12653
+ height: candidate.height
12654
+ }));
12655
+ }
12601
12656
  clearActiveSearch() {
12602
12657
  this.currentCollision = null;
12603
12658
  this.currentLabelToMove = null;
@@ -12614,7 +12669,9 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12614
12669
  return;
12615
12670
  }
12616
12671
  this.currentCollision = pair;
12617
- this.labelsToTry = [pair[1], pair[0]];
12672
+ this.labelsToTry = [pair[1], pair[0]].filter(
12673
+ (label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label)
12674
+ );
12618
12675
  this.beginSearchForLabel(this.labelsToTry.shift());
12619
12676
  return;
12620
12677
  }
@@ -12638,9 +12695,10 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12638
12695
  fixedLabel = labelB;
12639
12696
  }
12640
12697
  const obstacleLabels = [
12641
- ...this.outputNetLabelPlacements.filter(
12642
- (l) => l !== labelA && l !== labelB
12643
- ),
12698
+ ...[
12699
+ ...this.outputNetLabelPlacements,
12700
+ ...this.params.fixedNetLabelPlacements ?? []
12701
+ ].filter((l) => l !== labelA && l !== labelB),
12644
12702
  fixedLabel
12645
12703
  ];
12646
12704
  const status = this.checkCandidate(
@@ -14084,7 +14142,7 @@ var candidateIsClear = ({
14084
14142
  }
14085
14143
  for (const otherTrace of otherNetTraces) {
14086
14144
  const countIntersections = (path) => {
14087
- const intersections = /* @__PURE__ */ new Set();
14145
+ const intersections2 = /* @__PURE__ */ new Set();
14088
14146
  for (let i = 1; i < path.length; i++) {
14089
14147
  for (let j = 1; j < otherTrace.tracePath.length; j++) {
14090
14148
  const point = getSegmentIntersection3(
@@ -14094,10 +14152,10 @@ var candidateIsClear = ({
14094
14152
  otherTrace.tracePath[j]
14095
14153
  );
14096
14154
  if (point)
14097
- intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
14155
+ intersections2.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
14098
14156
  }
14099
14157
  }
14100
- return intersections.size;
14158
+ return intersections2.size;
14101
14159
  };
14102
14160
  if (countIntersections(candidateTrace.tracePath) > countIntersections(originalTrace.tracePath)) {
14103
14161
  return false;
@@ -15027,6 +15085,422 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
15027
15085
  }
15028
15086
  };
15029
15087
 
15088
+ // lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts
15089
+ import { getSegmentIntersection as getSegmentIntersection5 } from "@tscircuit/math-utils/line-intersections";
15090
+
15091
+ // lib/solvers/InlineNetLabelSolver/getInlineLabelObstacles.ts
15092
+ var getInlineTerminalBounds = (label) => {
15093
+ if (!label.stubTracePath) return null;
15094
+ const epsilon = 1e-6;
15095
+ return {
15096
+ minX: Math.min(...label.stubTracePath.map((p) => p.x)) - epsilon,
15097
+ maxX: Math.max(...label.stubTracePath.map((p) => p.x)) + epsilon,
15098
+ minY: Math.min(...label.stubTracePath.map((p) => p.y)) - epsilon,
15099
+ maxY: Math.max(...label.stubTracePath.map((p) => p.y)) + epsilon
15100
+ };
15101
+ };
15102
+ var getInlineLabelObstacles = (inputProblem, placements) => {
15103
+ const fixedLabels = placements.map((label) => ({
15104
+ globalConnNetId: label.globalConnNetId,
15105
+ netId: label.netId,
15106
+ pinIds: label.pinIds,
15107
+ mspConnectionPairIds: [],
15108
+ anchorPoint: label.anchorPoint,
15109
+ orientation: "y+",
15110
+ center: label.center,
15111
+ width: label.axis === "x" ? label.width : label.height,
15112
+ height: label.axis === "x" ? label.height : label.width
15113
+ }));
15114
+ const terminalTraces = placements.flatMap((label) => {
15115
+ if (!label.stubTracePath) return [];
15116
+ const chip = inputProblem.chips.find(
15117
+ (chip2) => chip2.pins.some((pin2) => label.pinIds.includes(pin2.pinId))
15118
+ );
15119
+ const pin = chip?.pins.find((pin2) => label.pinIds.includes(pin2.pinId));
15120
+ if (!chip || !pin) return [];
15121
+ const id = JSON.stringify([
15122
+ "inline-terminal",
15123
+ label.globalConnNetId,
15124
+ [...label.pinIds].sort()
15125
+ ]);
15126
+ return [
15127
+ {
15128
+ mspPairId: id,
15129
+ mspConnectionPairIds: [id],
15130
+ globalConnNetId: label.globalConnNetId,
15131
+ dcConnNetId: label.globalConnNetId,
15132
+ pinIds: label.pinIds,
15133
+ pins: [
15134
+ { ...pin, chipId: chip.chipId },
15135
+ { ...pin, ...label.stubTracePath[1], chipId: chip.chipId }
15136
+ ],
15137
+ tracePath: label.stubTracePath
15138
+ }
15139
+ ];
15140
+ });
15141
+ return { fixedLabels, terminalTraces };
15142
+ };
15143
+
15144
+ // lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts
15145
+ var EPS15 = 1e-6;
15146
+ var CLEARANCE = 0.05;
15147
+ var hits = (path, bounds) => path.slice(1).some((p, i) => segmentIntersectsRect(path[i], p, bounds));
15148
+ var same = (a, b) => Math.hypot(a.x - b.x, a.y - b.y) < EPS15;
15149
+ var translate = (p, axis, distance7) => ({
15150
+ ...p,
15151
+ [axis]: p[axis] + distance7
15152
+ });
15153
+ var intersections = (a, b) => a.slice(1).flatMap(
15154
+ (p, i) => b.slice(1).flatMap((q, j) => {
15155
+ const point = getSegmentIntersection5(a[i], p, b[j], q);
15156
+ return point ? [point] : [];
15157
+ })
15158
+ );
15159
+ function* getLocalTraceLabelShifts(inputProblem, output, pendingInlinePlacements = []) {
15160
+ const placements = [
15161
+ ...output.inlineNetLabelPlacements,
15162
+ ...pendingInlinePlacements
15163
+ ];
15164
+ const { fixedLabels, terminalTraces } = getInlineLabelObstacles(
15165
+ inputProblem,
15166
+ placements
15167
+ );
15168
+ const terminalBounds = placements.flatMap((label) => {
15169
+ const bounds = getInlineTerminalBounds(label);
15170
+ return bounds ? [{ label, bounds }] : [];
15171
+ });
15172
+ const obstacles = [
15173
+ ...inputProblem.chips.map((chip) => ({
15174
+ minX: chip.center.x - chip.width / 2,
15175
+ maxX: chip.center.x + chip.width / 2,
15176
+ minY: chip.center.y - chip.height / 2,
15177
+ maxY: chip.center.y + chip.height / 2
15178
+ })),
15179
+ ...(inputProblem.textBoxes ?? []).map((box) => getTextBoxBounds(box))
15180
+ ];
15181
+ const inlineBounds = fixedLabels.map((label) => ({
15182
+ minX: label.center.x - label.width / 2,
15183
+ maxX: label.center.x + label.width / 2,
15184
+ minY: label.center.y - label.height / 2,
15185
+ maxY: label.center.y + label.height / 2
15186
+ }));
15187
+ const pinPositions = [
15188
+ ...inputProblem.chips.flatMap((chip) => chip.pins),
15189
+ ...output.traces.flatMap((trace) => trace.pins)
15190
+ ];
15191
+ for (const trace of output.traces) {
15192
+ const path = simplifyPath(trace.tracePath);
15193
+ for (let i = 0; i < path.length - 1; i++) {
15194
+ const a = path[i], b = path[i + 1];
15195
+ const axis = Math.abs(a.x - b.x) < EPS15 ? "x" : "y";
15196
+ const along = axis === "x" ? "y" : "x";
15197
+ if (Math.abs(a[axis] - b[axis]) > EPS15 || same(a, b)) continue;
15198
+ const blocking = [
15199
+ ...output.netLabelPlacements.filter((label) => label.globalConnNetId !== trace.globalConnNetId).map(getAnchoredNetLabelRenderedBounds),
15200
+ ...inlineBounds,
15201
+ ...terminalBounds.filter(
15202
+ ({ label }) => !trace.pinIds.some((pinId) => label.pinIds.includes(pinId))
15203
+ ).map(({ bounds }) => bounds)
15204
+ ].filter((bounds) => segmentIntersectsRect(a, b, bounds));
15205
+ if (!blocking.length) continue;
15206
+ let low = Math.min(a[along], b[along]);
15207
+ let high = Math.max(a[along], b[along]);
15208
+ let expanded = true;
15209
+ while (expanded) {
15210
+ expanded = false;
15211
+ for (const other of output.traces) {
15212
+ if (other.globalConnNetId !== trace.globalConnNetId) continue;
15213
+ for (let j = 1; j < other.tracePath.length; j++) {
15214
+ const p = other.tracePath[j - 1], q = other.tracePath[j];
15215
+ if (Math.abs(p[axis] - a[axis]) > EPS15 || Math.abs(q[axis] - a[axis]) > EPS15)
15216
+ continue;
15217
+ const min = Math.min(p[along], q[along]), max = Math.max(p[along], q[along]);
15218
+ if (max < low - EPS15 || min > high + EPS15) continue;
15219
+ if (min < low || max > high) expanded = true;
15220
+ low = Math.min(low, min);
15221
+ high = Math.max(high, max);
15222
+ }
15223
+ }
15224
+ }
15225
+ const onRail = (p) => Math.abs(p[axis] - a[axis]) < EPS15 && p[along] >= low - EPS15 && p[along] <= high + EPS15;
15226
+ if (pinPositions.some(onRail)) continue;
15227
+ const minKey = axis === "x" ? "minX" : "minY";
15228
+ const maxKey = axis === "x" ? "maxX" : "maxY";
15229
+ const localWidth = Math.max(
15230
+ ...blocking.map((bounds) => bounds[maxKey] - bounds[minKey])
15231
+ );
15232
+ const railLabelBounds = output.netLabelPlacements.filter(
15233
+ (label) => label.globalConnNetId === trace.globalConnNetId && onRail(label.anchorPoint)
15234
+ ).map(getAnchoredNetLabelRenderedBounds);
15235
+ const coordinates = [
15236
+ ...new Set(
15237
+ blocking.flatMap((bounds) => [
15238
+ bounds[minKey] - CLEARANCE,
15239
+ bounds[maxKey] + CLEARANCE,
15240
+ ...railLabelBounds.filter((own) => boundsOverlap(own, bounds)).flatMap((own) => [
15241
+ a[axis] + bounds[minKey] - CLEARANCE - own[maxKey],
15242
+ a[axis] + bounds[maxKey] + CLEARANCE - own[minKey]
15243
+ ])
15244
+ ])
15245
+ )
15246
+ ].sort((x, y) => Math.abs(x - a[axis]) - Math.abs(y - a[axis]));
15247
+ for (const coordinate of coordinates) {
15248
+ const distance7 = coordinate - a[axis];
15249
+ if (Math.abs(distance7) > localWidth + 2 * CLEARANCE) continue;
15250
+ const traces = output.traces.map((other) => {
15251
+ if (other.globalConnNetId !== trace.globalConnNetId || !other.tracePath.some(onRail))
15252
+ return other;
15253
+ return {
15254
+ ...other,
15255
+ tracePath: other.tracePath.map(
15256
+ (p) => onRail(p) ? translate(p, axis, distance7) : p
15257
+ )
15258
+ };
15259
+ });
15260
+ const shiftedPaths = traces.filter((other, index) => other !== output.traces[index]).map((other) => other.tracePath);
15261
+ let labels = output.netLabelPlacements.map((label) => {
15262
+ if (label.globalConnNetId !== trace.globalConnNetId || !onRail(label.anchorPoint))
15263
+ return label;
15264
+ return {
15265
+ ...label,
15266
+ anchorPoint: translate(label.anchorPoint, axis, distance7),
15267
+ center: translate(label.center, axis, distance7)
15268
+ };
15269
+ });
15270
+ let blocked = false;
15271
+ const movedOwnLabelBounds = labels.flatMap(
15272
+ (label, index) => label !== output.netLabelPlacements[index] ? [getAnchoredNetLabelRenderedBounds(label)] : []
15273
+ );
15274
+ for (const [labelIndex, label] of labels.entries()) {
15275
+ if (label.globalConnNetId === trace.globalConnNetId) continue;
15276
+ const bounds = getAnchoredNetLabelRenderedBounds(label);
15277
+ const clearanceEdges = movedOwnLabelBounds.filter((own) => boundsOverlap(own, bounds)).map((own) => own[distance7 > 0 ? maxKey : minKey]);
15278
+ if (shiftedPaths.some((p) => hits(p, bounds)) && !output.traces.some(
15279
+ (other) => other.globalConnNetId === trace.globalConnNetId && hits(other.tracePath, bounds)
15280
+ ))
15281
+ clearanceEdges.push(coordinate);
15282
+ if (!clearanceEdges.length) continue;
15283
+ const connectorIndex = traces.findIndex(
15284
+ (candidate) => candidate.globalConnNetId === label.globalConnNetId && candidate.pinIds.length === 1 && candidate.tracePath.length === 3 && same(candidate.tracePath.at(-1), label.anchorPoint)
15285
+ );
15286
+ const connector = traces[connectorIndex];
15287
+ if (!connector) {
15288
+ const placementSolver = new NetLabelNetLabelCollisionSolver({
15289
+ inputProblem,
15290
+ traces: [...traces, ...terminalTraces],
15291
+ netLabelPlacements: labels,
15292
+ fixedNetLabelPlacements: fixedLabels,
15293
+ useRenderedLabelBounds: true
15294
+ });
15295
+ const nearby = placementSolver.getNearbyValidPlacements(
15296
+ label,
15297
+ localWidth + Math.max(label.width, label.height)
15298
+ )[0];
15299
+ if (!nearby) {
15300
+ blocked = true;
15301
+ break;
15302
+ }
15303
+ labels[labelIndex] = nearby;
15304
+ continue;
15305
+ }
15306
+ const [start, bend, end] = connector.tracePath;
15307
+ if (Math.abs(bend[axis] - end[axis]) > EPS15 || Math.abs(start[along] - bend[along]) > EPS15) {
15308
+ blocked = true;
15309
+ break;
15310
+ }
15311
+ const delta = distance7 > 0 ? Math.max(...clearanceEdges) + CLEARANCE - bounds[minKey] : Math.min(...clearanceEdges) - CLEARANCE - bounds[maxKey];
15312
+ if (Math.abs(delta) > localWidth + bounds[maxKey] - bounds[minKey] + 2 * CLEARANCE) {
15313
+ blocked = true;
15314
+ break;
15315
+ }
15316
+ const moved = {
15317
+ ...label,
15318
+ anchorPoint: translate(label.anchorPoint, axis, delta),
15319
+ center: translate(label.center, axis, delta)
15320
+ };
15321
+ labels[labelIndex] = moved;
15322
+ traces[connectorIndex] = {
15323
+ ...connector,
15324
+ tracePath: [start, translate(bend, axis, delta), moved.anchorPoint]
15325
+ };
15326
+ }
15327
+ if (blocked) continue;
15328
+ for (const [index, proposed] of traces.entries()) {
15329
+ const original = output.traces[index];
15330
+ if (proposed === original) continue;
15331
+ const candidate = proposed.tracePath;
15332
+ const previous = original.tracePath;
15333
+ if (candidate.slice(1).some(
15334
+ (p, index2) => Math.abs(p.x - candidate[index2].x) > EPS15 && Math.abs(p.y - candidate[index2].y) > EPS15
15335
+ )) {
15336
+ blocked = true;
15337
+ break;
15338
+ }
15339
+ const dot = (a2, b2, c, d) => (b2.x - a2.x) * (d.x - c.x) + (b2.y - a2.y) * (d.y - c.y);
15340
+ if (dot(previous[0], previous[1], candidate[0], candidate[1]) <= EPS15 || dot(
15341
+ previous.at(-1),
15342
+ previous.at(-2),
15343
+ candidate.at(-1),
15344
+ candidate.at(-2)
15345
+ ) <= EPS15) {
15346
+ blocked = true;
15347
+ break;
15348
+ }
15349
+ const junctions = output.traces.filter(
15350
+ (other) => other !== original && other.globalConnNetId === original.globalConnNetId
15351
+ ).flatMap((other) => [
15352
+ ...other.tracePath,
15353
+ ...intersections(previous, other.tracePath)
15354
+ ]).filter((p) => tracePathContainsPoint(previous, p)).map(
15355
+ (p) => original.globalConnNetId === trace.globalConnNetId && onRail(p) ? translate(p, axis, distance7) : p
15356
+ );
15357
+ const protectedPoints = [
15358
+ ...pinPositions.filter((p) => tracePathContainsPoint(previous, p)),
15359
+ ...junctions
15360
+ ];
15361
+ if (protectedPoints.some(
15362
+ (p) => !tracePathContainsPoint(candidate, p)
15363
+ ) || pinPositions.some((p) => {
15364
+ const clearance = {
15365
+ minX: p.x - CLEARANCE,
15366
+ maxX: p.x + CLEARANCE,
15367
+ minY: p.y - CLEARANCE,
15368
+ maxY: p.y + CLEARANCE
15369
+ };
15370
+ return hits(candidate, clearance) && !hits(previous, clearance);
15371
+ }) || obstacles.some(
15372
+ (bounds) => hits(candidate, bounds) && !hits(previous, bounds)
15373
+ )) {
15374
+ blocked = true;
15375
+ break;
15376
+ }
15377
+ for (const other of [...traces, ...terminalTraces]) {
15378
+ if (other.mspPairId === proposed.mspPairId || other.globalConnNetId === proposed.globalConnNetId)
15379
+ continue;
15380
+ const originalOther = output.traces.find((t) => t.mspPairId === other.mspPairId) ?? other;
15381
+ if (doesPathCoincideWithTraces(candidate, [other]) || intersections(candidate, other.tracePath).length > intersections(previous, originalOther.tracePath).length) {
15382
+ blocked = true;
15383
+ break;
15384
+ }
15385
+ }
15386
+ if (blocked) break;
15387
+ }
15388
+ if (blocked) continue;
15389
+ for (const [index, label] of labels.entries()) {
15390
+ if (label === output.netLabelPlacements[index]) continue;
15391
+ const bounds = getAnchoredNetLabelRenderedBounds(label);
15392
+ if (obstacles.some((obstacle) => boundsOverlap(bounds, obstacle)) || inlineBounds.some((other) => boundsOverlap(bounds, other))) {
15393
+ blocked = true;
15394
+ break;
15395
+ }
15396
+ if (labels.some(
15397
+ (other, j) => index !== j && boundsOverlap(bounds, getAnchoredNetLabelRenderedBounds(other))
15398
+ )) {
15399
+ blocked = true;
15400
+ break;
15401
+ }
15402
+ if ([...traces, ...terminalTraces].some(
15403
+ (other) => other.globalConnNetId !== label.globalConnNetId && hits(other.tracePath, bounds)
15404
+ )) {
15405
+ blocked = true;
15406
+ break;
15407
+ }
15408
+ }
15409
+ if (!blocked) yield { ...output, traces, netLabelPlacements: labels };
15410
+ }
15411
+ }
15412
+ }
15413
+ }
15414
+
15415
+ // lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions.ts
15416
+ var sameLabel = (a, b) => {
15417
+ const first = [...a.pinIds].sort();
15418
+ const second = [...b.pinIds].sort();
15419
+ return a.globalConnNetId === b.globalConnNetId && first.length === second.length && first.every((pinId, index) => pinId === second[index]);
15420
+ };
15421
+ var sameOutputLabelCollision = (a, b) => {
15422
+ if (a.kind === "label-label" && b.kind === "label-label")
15423
+ return sameLabel(a.labels[0], b.labels[0]) && sameLabel(a.labels[1], b.labels[1]) || sameLabel(a.labels[0], b.labels[1]) && sameLabel(a.labels[1], b.labels[0]);
15424
+ if (a.kind !== "trace-label" || b.kind !== "trace-label") return false;
15425
+ const sameTrace = a.trace.kind === "routed" && b.trace.kind === "routed" ? a.trace.id === b.trace.id : a.trace.kind === "terminal" && b.trace.kind === "terminal" && sameLabel(a.trace.label, b.trace.label);
15426
+ return sameTrace && a.target === b.target && sameLabel(a.label, b.label);
15427
+ };
15428
+ var getOutputLabelCollisions = (output) => {
15429
+ const labels = [
15430
+ ...output.netLabelPlacements,
15431
+ ...output.inlineNetLabelPlacements
15432
+ ].map((label) => ({
15433
+ label,
15434
+ bounds: "axis" in label ? {
15435
+ minX: label.center.x - (label.axis === "x" ? label.width : label.height) / 2,
15436
+ maxX: label.center.x + (label.axis === "x" ? label.width : label.height) / 2,
15437
+ minY: label.center.y - (label.axis === "x" ? label.height : label.width) / 2,
15438
+ maxY: label.center.y + (label.axis === "x" ? label.height : label.width) / 2
15439
+ } : getAnchoredNetLabelRenderedBounds(label)
15440
+ }));
15441
+ const collisions = [];
15442
+ const add = (collision) => {
15443
+ if (!collisions.some((other) => sameOutputLabelCollision(collision, other)))
15444
+ collisions.push(collision);
15445
+ };
15446
+ for (let i = 0; i < labels.length; i++)
15447
+ for (const other of labels.slice(i + 1)) {
15448
+ if (boundsOverlap(labels[i].bounds, other.bounds))
15449
+ add({ kind: "label-label", labels: [labels[i].label, other.label] });
15450
+ }
15451
+ const traces = [
15452
+ ...output.traces.map((trace) => ({
15453
+ ...trace,
15454
+ identity: { kind: "routed", id: trace.mspPairId }
15455
+ })),
15456
+ ...output.inlineNetLabelPlacements.flatMap(
15457
+ (label) => label.stubTracePath ? [
15458
+ {
15459
+ identity: { kind: "terminal", label },
15460
+ globalConnNetId: label.globalConnNetId,
15461
+ tracePath: label.stubTracePath
15462
+ }
15463
+ ] : []
15464
+ )
15465
+ ];
15466
+ for (const trace of traces)
15467
+ for (const { label, bounds } of labels) {
15468
+ if (("axis" in label || label.globalConnNetId !== trace.globalConnNetId) && trace.tracePath.slice(1).some(
15469
+ (end, index) => segmentIntersectsRect(trace.tracePath[index], end, bounds)
15470
+ ))
15471
+ add({
15472
+ kind: "trace-label",
15473
+ trace: trace.identity,
15474
+ label,
15475
+ target: "text"
15476
+ });
15477
+ }
15478
+ for (const label of output.inlineNetLabelPlacements) {
15479
+ const bounds = getInlineTerminalBounds(label);
15480
+ if (!bounds) continue;
15481
+ for (const trace of output.traces) {
15482
+ if (trace.pinIds.some((pinId) => label.pinIds.includes(pinId))) continue;
15483
+ if (trace.tracePath.slice(1).some(
15484
+ (end, index) => segmentIntersectsRect(trace.tracePath[index], end, bounds)
15485
+ ))
15486
+ add({
15487
+ kind: "trace-label",
15488
+ trace: { kind: "routed", id: trace.mspPairId },
15489
+ label,
15490
+ target: "terminal"
15491
+ });
15492
+ }
15493
+ }
15494
+ return collisions;
15495
+ };
15496
+
15497
+ // lib/solvers/InlineNetLabelSolver/isLabelRepresentedInline.ts
15498
+ var isLabelRepresentedInline = (label, inlinePlacements) => label.pinIds.length > 0 && label.pinIds.every(
15499
+ (pinId) => inlinePlacements.some(
15500
+ (inline) => inline.globalConnNetId === label.globalConnNetId && inline.pinIds.includes(pinId)
15501
+ )
15502
+ );
15503
+
15030
15504
  // lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts
15031
15505
  var getStubDirection = (path) => {
15032
15506
  const [start, end] = path;
@@ -15102,12 +15576,7 @@ var alignPortOnlyInlineNetLabelStubs = ({
15102
15576
  groups.set(groupKey, group);
15103
15577
  }
15104
15578
  const alignedPlacements = [...placements];
15105
- const supersededGlobalNetIds = new Set(
15106
- placements.map((placement) => placement.globalConnNetId)
15107
- );
15108
- const retainedAnchoredLabelBounds = netLabelPlacements.filter(
15109
- (placement) => !supersededGlobalNetIds.has(placement.globalConnNetId)
15110
- ).map(getLabelBounds2);
15579
+ const retainedAnchoredLabelBounds = netLabelPlacements.filter((placement) => !isLabelRepresentedInline(placement, placements)).map(getLabelBounds2);
15111
15580
  for (const group of groups.values()) {
15112
15581
  if (group.length < 2) continue;
15113
15582
  const targetLength = Math.max(
@@ -15216,22 +15685,6 @@ var getAxisAlignedSegments2 = (path, epsilon = 1e-6) => {
15216
15685
  return segments.sort((a, b) => b.length - a.length);
15217
15686
  };
15218
15687
 
15219
- // lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts
15220
- var getAnchoredNetLabelRenderedBounds = (placement) => {
15221
- if (placement.orientation !== "x-" && placement.orientation !== "x+") {
15222
- return getRectBounds(placement.center, placement.width, placement.height);
15223
- }
15224
- const width = Math.max(placement.width, placement.height);
15225
- const height = NET_LABEL_HORIZONTAL_HEIGHT;
15226
- const center = getCenterFromAnchor(
15227
- placement.anchorPoint,
15228
- placement.orientation,
15229
- width,
15230
- height
15231
- );
15232
- return getRectBounds(center, width, height);
15233
- };
15234
-
15235
15688
  // lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts
15236
15689
  var LABEL_CLEARANCE2 = 0.05;
15237
15690
  var POINT_EPSILON = 1e-6;
@@ -15279,29 +15732,37 @@ var isPointOnPath2 = (point, path) => {
15279
15732
  return false;
15280
15733
  };
15281
15734
  var getRequiredOutwardDistance = (label, inlineBounds) => {
15282
- const labelBounds = getAnchoredNetLabelRenderedBounds(label);
15283
- if (label.orientation === "x-" || label.orientation === "x+") {
15284
- const nearby2 = inlineBounds.filter(
15285
- (bounds) => labelBounds.minY < bounds.maxY && labelBounds.maxY > bounds.minY
15735
+ let distance7 = 0;
15736
+ for (let iteration = 0; iteration < inlineBounds.length; iteration++) {
15737
+ const labelBounds = getAnchoredNetLabelRenderedBounds(
15738
+ moveLabel(label, label.orientation, distance7)
15739
+ );
15740
+ const horizontal = label.orientation === "x-" || label.orientation === "x+";
15741
+ const overlapping = inlineBounds.filter(
15742
+ (bounds) => boundsOverlap(labelBounds, {
15743
+ minX: bounds.minX - (horizontal ? LABEL_CLEARANCE2 : 0),
15744
+ maxX: bounds.maxX + (horizontal ? LABEL_CLEARANCE2 : 0),
15745
+ minY: bounds.minY - (horizontal ? 0 : LABEL_CLEARANCE2),
15746
+ maxY: bounds.maxY + (horizontal ? 0 : LABEL_CLEARANCE2)
15747
+ })
15286
15748
  );
15287
- if (nearby2.length === 0) return 0;
15288
- if (label.orientation === "x-") {
15289
- const targetMaxX = Math.min(...nearby2.map((bounds) => bounds.minX));
15290
- return Math.max(0, labelBounds.maxX - targetMaxX + LABEL_CLEARANCE2);
15749
+ if (overlapping.length === 0) break;
15750
+ switch (label.orientation) {
15751
+ case "x-":
15752
+ distance7 += labelBounds.maxX - Math.min(...overlapping.map((b) => b.minX)) + LABEL_CLEARANCE2;
15753
+ break;
15754
+ case "x+":
15755
+ distance7 += Math.max(...overlapping.map((b) => b.maxX)) - labelBounds.minX + LABEL_CLEARANCE2;
15756
+ break;
15757
+ case "y-":
15758
+ distance7 += labelBounds.maxY - Math.min(...overlapping.map((b) => b.minY)) + LABEL_CLEARANCE2;
15759
+ break;
15760
+ case "y+":
15761
+ distance7 += Math.max(...overlapping.map((b) => b.maxY)) - labelBounds.minY + LABEL_CLEARANCE2;
15762
+ break;
15291
15763
  }
15292
- const targetMinX = Math.max(...nearby2.map((bounds) => bounds.maxX));
15293
- return Math.max(0, targetMinX - labelBounds.minX + LABEL_CLEARANCE2);
15294
- }
15295
- const nearby = inlineBounds.filter(
15296
- (bounds) => labelBounds.minX < bounds.maxX && labelBounds.maxX > bounds.minX
15297
- );
15298
- if (nearby.length === 0) return 0;
15299
- if (label.orientation === "y-") {
15300
- const targetMaxY = Math.min(...nearby.map((bounds) => bounds.minY));
15301
- return Math.max(0, labelBounds.maxY - targetMaxY + LABEL_CLEARANCE2);
15302
15764
  }
15303
- const targetMinY = Math.max(...nearby.map((bounds) => bounds.maxY));
15304
- return Math.max(0, targetMinY - labelBounds.minY + LABEL_CLEARANCE2);
15765
+ return distance7;
15305
15766
  };
15306
15767
  var moveLabel = (label, orientation, distance7) => {
15307
15768
  const direction = dir(orientation);
@@ -15336,10 +15797,9 @@ var boundsGapOnPerpendicularAxis = (a, b, orientation) => {
15336
15797
  return Math.max(0, a.minX - b.maxX, b.minX - a.maxX);
15337
15798
  };
15338
15799
  var sharesOwnerChip = (label, ownerChipIds, chipIdByPinId) => label.pinIds.some((pinId) => ownerChipIds.has(chipIdByPinId.get(pinId) ?? ""));
15339
- var isGeneratedLabelConnector = (trace) => trace.mspPairId.startsWith("available-net-orientation-") || trace.mspPairId.startsWith("inline-net-label-clearance-");
15340
- var findConnectorTraceIndex = (label, traces) => traces.findIndex((trace) => {
15800
+ var findConnectorTraceIndex = (label, traces, connectorTraceIds) => traces.findIndex((trace) => {
15341
15801
  if (trace.globalConnNetId !== label.globalConnNetId) return false;
15342
- if (!isGeneratedLabelConnector(trace)) return false;
15802
+ if (!connectorTraceIds.has(trace.mspPairId)) return false;
15343
15803
  const first = trace.tracePath[0];
15344
15804
  const last = trace.tracePath.at(-1);
15345
15805
  return Boolean(
@@ -15422,8 +15882,10 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15422
15882
  inputProblem,
15423
15883
  traces,
15424
15884
  netLabelPlacements,
15425
- inlineNetLabelPlacements
15885
+ inlineNetLabelPlacements,
15886
+ netLabelConnectorTraceIds = /* @__PURE__ */ new Set()
15426
15887
  }) => {
15888
+ const connectorTraceIds = new Set(netLabelConnectorTraceIds);
15427
15889
  const outputTraces = traces.map((trace) => ({
15428
15890
  ...trace,
15429
15891
  tracePath: trace.tracePath.map((point) => ({ ...point }))
@@ -15467,7 +15929,11 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15467
15929
  moveLabel(obstacle, trigger.orientation, existingObstacleDistance)
15468
15930
  );
15469
15931
  if (!boundsOverlap(movingBounds, obstacleBounds)) continue;
15470
- if (!sharesOwnerChip(obstacle, ownerChipIds, chipIdByPinId) || findConnectorTraceIndex(obstacle, outputTraces) === -1 && !canAddConnectorAtAnchor(obstacle, outputTraces, pinMap)) {
15932
+ if (!sharesOwnerChip(obstacle, ownerChipIds, chipIdByPinId) || findConnectorTraceIndex(
15933
+ obstacle,
15934
+ outputTraces,
15935
+ connectorTraceIds
15936
+ ) === -1 && !canAddConnectorAtAnchor(obstacle, outputTraces, pinMap)) {
15471
15937
  failed = true;
15472
15938
  break;
15473
15939
  }
@@ -15539,7 +16005,11 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15539
16005
  const connectorUpdates = [];
15540
16006
  for (const [labelIndex, movedLabel] of proposals) {
15541
16007
  const label = outputLabels[labelIndex];
15542
- const connectorIndex = findConnectorTraceIndex(label, outputTraces);
16008
+ const connectorIndex = findConnectorTraceIndex(
16009
+ label,
16010
+ outputTraces,
16011
+ connectorTraceIds
16012
+ );
15543
16013
  if (connectorIndex === -1 && !canAddConnectorAtAnchor(label, outputTraces, pinMap)) {
15544
16014
  failed = true;
15545
16015
  break;
@@ -15589,6 +16059,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15589
16059
  movedLabelIndices.add(labelIndex);
15590
16060
  }
15591
16061
  for (const update of connectorUpdates) {
16062
+ connectorTraceIds.add(update.trace.mspPairId);
15592
16063
  if (update.connectorIndex === -1) outputTraces.push(update.trace);
15593
16064
  else outputTraces[update.connectorIndex] = update.trace;
15594
16065
  }
@@ -15596,7 +16067,8 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15596
16067
  return {
15597
16068
  traces: outputTraces,
15598
16069
  netLabelPlacements: outputLabels,
15599
- movedLabelCount: movedLabelIndices.size
16070
+ movedLabelCount: movedLabelIndices.size,
16071
+ netLabelConnectorTraceIds: connectorTraceIds
15600
16072
  };
15601
16073
  };
15602
16074
 
@@ -15798,7 +16270,7 @@ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
15798
16270
  };
15799
16271
 
15800
16272
  // lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts
15801
- import { getSegmentIntersection as getSegmentIntersection5 } from "@tscircuit/math-utils/line-intersections";
16273
+ import { getSegmentIntersection as getSegmentIntersection6 } from "@tscircuit/math-utils/line-intersections";
15802
16274
 
15803
16275
  // lib/utils/pathIntersectsRenderedLabel.ts
15804
16276
  import {
@@ -15826,31 +16298,31 @@ var pathIntersectsRenderedLabel = (path, label) => {
15826
16298
  };
15827
16299
 
15828
16300
  // lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts
15829
- var EPS15 = 1e-6;
16301
+ var EPS16 = 1e-6;
15830
16302
  var getPathLength7 = (path) => path.slice(1).reduce((length, point, pointIndex) => {
15831
16303
  const previousPoint = path[pointIndex];
15832
16304
  return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
15833
16305
  }, 0);
15834
16306
  var pathsEqual = (first, second) => first.length === second.length && first.every(
15835
- (point, index) => Math.abs(point.x - second[index].x) <= EPS15 && Math.abs(point.y - second[index].y) <= EPS15
16307
+ (point, index) => Math.abs(point.x - second[index].x) <= EPS16 && Math.abs(point.y - second[index].y) <= EPS16
15836
16308
  );
15837
- var isStrictlySimpler = (candidate, current) => candidate.length < current.length && getPathLength7(candidate) <= getPathLength7(current) + EPS15;
16309
+ var isStrictlySimpler = (candidate, current) => candidate.length < current.length && getPathLength7(candidate) <= getPathLength7(current) + EPS16;
15838
16310
  var getIntersectionKeys = (path, otherPath) => {
15839
- const intersections = /* @__PURE__ */ new Set();
16311
+ const intersections2 = /* @__PURE__ */ new Set();
15840
16312
  for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
15841
16313
  for (let otherPathIndex = 0; otherPathIndex < otherPath.length - 1; otherPathIndex++) {
15842
- const point = getSegmentIntersection5(
16314
+ const point = getSegmentIntersection6(
15843
16315
  path[pathIndex],
15844
16316
  path[pathIndex + 1],
15845
16317
  otherPath[otherPathIndex],
15846
16318
  otherPath[otherPathIndex + 1]
15847
16319
  );
15848
16320
  if (point) {
15849
- intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
16321
+ intersections2.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
15850
16322
  }
15851
16323
  }
15852
16324
  }
15853
- return intersections;
16325
+ return intersections2;
15854
16326
  };
15855
16327
  var introducesNewCrossings = (candidatePath, currentPath, otherPaths) => otherPaths.some((otherPath) => {
15856
16328
  const currentIntersections = getIntersectionKeys(currentPath, otherPath);
@@ -15927,9 +16399,7 @@ var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
15927
16399
  var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
15928
16400
  var INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
15929
16401
  var INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4;
15930
- var AVAILABLE_NET_ORIENTATION_TRACE_PREFIX = "available-net-orientation-";
15931
- var isGeneratedAnchoredLabelConnector = (trace) => trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_TRACE_PREFIX);
15932
- var getPinPairKey2 = (pinIds) => [...pinIds].sort().join("::");
16402
+ var getPinPairKey2 = (pinIds) => JSON.stringify([...pinIds].sort());
15933
16403
  var getTraceLength2 = (trace) => {
15934
16404
  let length = 0;
15935
16405
  for (let index = 0; index < trace.tracePath.length - 1; index++) {
@@ -15950,21 +16420,26 @@ var getInlinePlacementBounds = (placement) => {
15950
16420
  maxY: placement.center.y + renderedHeight / 2
15951
16421
  };
15952
16422
  };
15953
- var InlineNetLabelSolver = class extends BaseSolver {
15954
- inputProblem;
15955
- traces;
15956
- inputNetLabelPlacements;
15957
- completedReroutes;
15958
- inlineNetLabelPlacements = [];
15959
- /** Eligible connections still waiting to be processed. */
15960
- queuedConnections;
15961
- tracesByPinPairKey;
15962
- globalConnMap;
15963
- supersededTraceIdsByGlobalConnNetId = /* @__PURE__ */ new Map();
15964
- hasAlignedPortOnlyStubs = false;
15965
- postProcessedOutput;
16423
+ var getInlinePlacementKey = (placement) => JSON.stringify([
16424
+ placement.globalConnNetId,
16425
+ [...placement.pinIds].sort(),
16426
+ placement.mspPairId ?? null
16427
+ ]);
16428
+ var terminalStubIntersectsTrace = (path, trace) => {
16429
+ const clearance = INLINE_NET_LABEL_STUB_TRACE_CLEARANCE - GEOMETRY_EPSILON;
16430
+ const bounds = {
16431
+ minX: Math.min(path[0].x, path[1].x) - clearance,
16432
+ maxX: Math.max(path[0].x, path[1].x) + clearance,
16433
+ minY: Math.min(path[0].y, path[1].y) - clearance,
16434
+ maxY: Math.max(path[0].y, path[1].y) + clearance
16435
+ };
16436
+ return doesPathIntersectBounds3(trace.tracePath, bounds);
16437
+ };
16438
+ var InlineNetLabelSolver = class _InlineNetLabelSolver extends BaseSolver {
15966
16439
  constructor(input) {
15967
16440
  super();
16441
+ this.input = input;
16442
+ this.netLabelConnectorTraceIds = input.netLabelConnectorTraceIds ?? /* @__PURE__ */ new Set();
15968
16443
  this.inputProblem = input.inputProblem;
15969
16444
  this.traces = input.traces;
15970
16445
  this.inputNetLabelPlacements = input.netLabelPlacements;
@@ -15974,7 +16449,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
15974
16449
  ).netConnMap;
15975
16450
  this.queuedConnections = [
15976
16451
  ...this.inputProblem.directConnections.filter(
15977
- (connection) => connection.allowInlineNetLabel && connection.netId
16452
+ (connection) => connection.allowInlineNetLabel && connection.netId && // An opted-in named net owns all of its connected components.
16453
+ // Do not plan the same pair again through its direct connection.
16454
+ !this.inputProblem.netConnections.some(
16455
+ (net) => net.allowInlineNetLabel && net.netId && connection.pinIds.every((pin) => net.pinIds.includes(pin))
16456
+ )
15978
16457
  ).map((connection) => ({
15979
16458
  kind: "direct_connection",
15980
16459
  connection
@@ -15988,7 +16467,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
15988
16467
  ];
15989
16468
  this.tracesByPinPairKey = /* @__PURE__ */ new Map();
15990
16469
  for (const trace of this.traces) {
15991
- if (isGeneratedAnchoredLabelConnector(trace)) continue;
16470
+ if (this.netLabelConnectorTraceIds.has(trace.mspPairId)) continue;
15992
16471
  const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
15993
16472
  const existing = this.tracesByPinPairKey.get(key);
15994
16473
  if (existing) {
@@ -15998,13 +16477,32 @@ var InlineNetLabelSolver = class extends BaseSolver {
15998
16477
  }
15999
16478
  }
16000
16479
  }
16480
+ input;
16481
+ inputProblem;
16482
+ traces;
16483
+ inputNetLabelPlacements;
16484
+ completedReroutes;
16485
+ inlineNetLabelPlacements = [];
16486
+ /** Eligible connections still waiting to be processed. */
16487
+ queuedConnections;
16488
+ netLabelConnectorTraceIds;
16489
+ tracesByPinPairKey;
16490
+ globalConnMap;
16491
+ inlineConversions = [];
16492
+ hasAlignedPortOnlyStubs = false;
16493
+ postProcessedOutput;
16494
+ refinementComplete = false;
16495
+ refinement;
16496
+ currentProposal;
16001
16497
  getConstructorParams() {
16002
16498
  return [
16003
16499
  {
16004
16500
  inputProblem: this.inputProblem,
16005
16501
  traces: this.traces,
16006
16502
  netLabelPlacements: this.inputNetLabelPlacements,
16007
- completedReroutes: this.completedReroutes
16503
+ completedReroutes: this.completedReroutes,
16504
+ resolveTraceCollisions: this.input.resolveTraceCollisions,
16505
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
16008
16506
  }
16009
16507
  ];
16010
16508
  }
@@ -16025,16 +16523,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
16025
16523
  netConnections
16026
16524
  )) {
16027
16525
  this.inlineNetLabelPlacements.push(...conversion.placements);
16028
- const supersededTraceIds = this.supersededTraceIdsByGlobalConnNetId.get(
16029
- conversion.globalConnNetId
16030
- ) ?? /* @__PURE__ */ new Set();
16031
- for (const traceId of conversion.supersededTraceIds) {
16032
- supersededTraceIds.add(traceId);
16033
- }
16034
- this.supersededTraceIdsByGlobalConnNetId.set(
16035
- conversion.globalConnNetId,
16036
- supersededTraceIds
16037
- );
16526
+ this.inlineConversions.push(conversion);
16038
16527
  }
16039
16528
  return;
16040
16529
  }
@@ -16048,11 +16537,12 @@ var InlineNetLabelSolver = class extends BaseSolver {
16048
16537
  });
16049
16538
  if (placement) this.inlineNetLabelPlacements.push(placement);
16050
16539
  } else {
16051
- const terminalPlacements = connection.pinIds.map(
16052
- (pinId) => this.computeTerminalInlinePlacement(connection, pinId)
16053
- );
16054
- if (terminalPlacements.every((placement) => placement !== null)) {
16055
- this.inlineNetLabelPlacements.push(...terminalPlacements);
16540
+ for (const pinId of connection.pinIds) {
16541
+ const placement = this.computeTerminalInlinePlacement(
16542
+ connection,
16543
+ pinId
16544
+ );
16545
+ if (placement) this.inlineNetLabelPlacements.push(placement);
16056
16546
  }
16057
16547
  }
16058
16548
  return;
@@ -16071,13 +16561,17 @@ var InlineNetLabelSolver = class extends BaseSolver {
16071
16561
  this.postProcessedOutput = this.buildPostProcessedOutput();
16072
16562
  return;
16073
16563
  }
16564
+ if (this.input.resolveTraceCollisions && !this.refinementComplete) {
16565
+ this.stepTraceRefinement();
16566
+ return;
16567
+ }
16074
16568
  this.solved = true;
16075
16569
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
16076
16570
  }
16077
16571
  /**
16078
16572
  * Plans all opted-in multi-pin nets together. Short routed components can
16079
16573
  * mutually block the outward endpoint stubs that replace them, so tentative
16080
- * replacements are ignored as obstacles only while their owning net still
16574
+ * replacements are ignored as obstacles only while their connected component still
16081
16575
  * converts successfully. Failed conversions retain both their traces and
16082
16576
  * their conventional labels, and the remaining conversions are retried.
16083
16577
  */
@@ -16118,13 +16612,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
16118
16612
  }));
16119
16613
  const nextIgnoredTraceIds = new Set(
16120
16614
  conversions.flatMap(
16121
- ({ conversion }) => conversion ? [...conversion.supersededTraceIds] : []
16615
+ ({ conversion }) => conversion.flatMap((component) => [...component.supersededTraceIds])
16122
16616
  )
16123
16617
  );
16124
16618
  if (setsEqual(ignoredTraceIds, nextIgnoredTraceIds)) {
16125
- return conversions.flatMap(
16126
- ({ conversion }) => conversion ? [conversion] : []
16127
- );
16619
+ return conversions.flatMap(({ conversion }) => conversion);
16128
16620
  }
16129
16621
  ignoredTraceIds = nextIgnoredTraceIds;
16130
16622
  }
@@ -16154,7 +16646,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
16154
16646
  // anchored label. They are removed when that label becomes inline, so
16155
16647
  // treating them as routed circuitry would leave text on a deleted wire.
16156
16648
  traces: this.traces.filter(
16157
- (trace) => !isGeneratedAnchoredLabelConnector(trace)
16649
+ (trace) => !this.netLabelConnectorTraceIds.has(trace.mspPairId)
16158
16650
  )
16159
16651
  }).map((component) => ({
16160
16652
  pinIds: component.pinIds,
@@ -16180,14 +16672,14 @@ var InlineNetLabelSolver = class extends BaseSolver {
16180
16672
  * Converts every conventional label representation for an opted-in named
16181
16673
  * net. Routed connected components become labels on their representative
16182
16674
  * trace; disconnected pins become outward terminal stubs. The conversion is
16183
- * atomic so one blocked candidate cannot leave mixed label styles on a net.
16675
+ * atomic within each routed component, so replacing its traces cannot strand
16676
+ * a pin. Disconnected components can keep their own label style.
16184
16677
  */
16185
16678
  computeNetConnectionInlinePlacements(connection, ignoredTraceIds, forcedTerminalTraceIds) {
16186
16679
  const components = this.getNetConnectionComponents(connection);
16187
16680
  const globalConnNetId = this.getGlobalConnNetId(connection);
16188
- if (!globalConnNetId) return null;
16189
- const inlinePlacements = [];
16190
- const supersededTraceIds = /* @__PURE__ */ new Set();
16681
+ if (!globalConnNetId) return [];
16682
+ const conversions = [];
16191
16683
  for (const component of components) {
16192
16684
  const shouldUseTerminalStubs = component.traces.some(
16193
16685
  (trace) => forcedTerminalTraceIds.has(trace.mspPairId)
@@ -16205,14 +16697,18 @@ var InlineNetLabelSolver = class extends BaseSolver {
16205
16697
  }
16206
16698
  }
16207
16699
  if (inlinePlacement) {
16208
- inlinePlacements.push(inlinePlacement);
16700
+ conversions.push({
16701
+ globalConnNetId,
16702
+ placements: [inlinePlacement],
16703
+ supersededTraceIds: /* @__PURE__ */ new Set()
16704
+ });
16209
16705
  continue;
16210
16706
  }
16211
16707
  if (component.traces.length > 0 && !shouldUseTerminalStubs) {
16212
- return null;
16708
+ continue;
16213
16709
  }
16214
16710
  if (component.labeledPinIds.length !== component.pinIds.length) {
16215
- return null;
16711
+ continue;
16216
16712
  }
16217
16713
  const terminalPlacements = component.pinIds.map(
16218
16714
  (pinId) => this.computeTerminalInlinePlacement(
@@ -16223,26 +16719,41 @@ var InlineNetLabelSolver = class extends BaseSolver {
16223
16719
  )
16224
16720
  );
16225
16721
  if (terminalPlacements.some((placement) => placement === null)) {
16226
- return null;
16722
+ continue;
16227
16723
  }
16228
- inlinePlacements.push(
16229
- ...terminalPlacements.filter(
16230
- (placement) => placement !== null
16724
+ conversions.push({
16725
+ globalConnNetId,
16726
+ placements: terminalPlacements,
16727
+ supersededTraceIds: new Set(
16728
+ component.traces.map((trace) => trace.mspPairId)
16231
16729
  )
16232
- );
16233
- for (const trace of component.traces) {
16234
- supersededTraceIds.add(trace.mspPairId);
16730
+ });
16731
+ }
16732
+ return conversions;
16733
+ }
16734
+ /** Prefer the normal gap, then center text in a tighter parallel-wire slot. */
16735
+ getInlineOffsets(anchor, axis, side, width, height, traces, ignoredTraceIds) {
16736
+ const normal = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
16737
+ const across = axis === "x" ? "y" : "x";
16738
+ const sign = side === "x+" || side === "y+" ? 1 : -1;
16739
+ let gap = Infinity;
16740
+ for (const trace of traces) {
16741
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
16742
+ for (const segment of getAxisAlignedSegments2(trace.tracePath)) {
16743
+ if (segment.axis !== axis) continue;
16744
+ if (Math.max(segment.start[axis], segment.end[axis]) <= anchor[axis] - width / 2 || Math.min(segment.start[axis], segment.end[axis]) >= anchor[axis] + width / 2)
16745
+ continue;
16746
+ const distance7 = sign * (segment.start[across] - anchor[across]);
16747
+ if (distance7 > GEOMETRY_EPSILON) gap = Math.min(gap, distance7);
16235
16748
  }
16236
16749
  }
16237
- return { globalConnNetId, placements: inlinePlacements, supersededTraceIds };
16750
+ return gap > height + GEOMETRY_EPSILON && gap / 2 < normal - GEOMETRY_EPSILON ? [normal, gap / 2] : [normal];
16238
16751
  }
16239
16752
  /**
16240
- * Converts one conventional endpoint placement into an inline label on a
16241
- * generated outward stub. The stub follows the pin's true facing direction;
16242
- * an anchored label may finish in another direction after an elbow, which is
16243
- * not the direction a terminal stub should leave the pin.
16753
+ * Converts one conventional endpoint placement into an inline label on an
16754
+ * outward stub that follows the pin's true facing direction.
16244
16755
  */
16245
- computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set()) {
16756
+ computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set(), obstacles) {
16246
16757
  if (!connection.netId) return null;
16247
16758
  const anchoredPlacement = this.inputNetLabelPlacements.find(
16248
16759
  (placement) => placement.netId === connection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
@@ -16265,7 +16776,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
16265
16776
  start,
16266
16777
  intendedEnd,
16267
16778
  minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
16268
- traces: this.traces,
16779
+ traces: obstacles?.traces ?? this.traces,
16269
16780
  ownGlobalConnNetId: globalConnNetId,
16270
16781
  ignoredTraceIds
16271
16782
  });
@@ -16275,42 +16786,52 @@ var InlineNetLabelSolver = class extends BaseSolver {
16275
16786
  x: (start.x + end.x) / 2,
16276
16787
  y: (start.y + end.y) / 2
16277
16788
  };
16278
- const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
16279
16789
  const sides = axis === "x" ? ["y+", "y-"] : ["x-", "x+"];
16280
16790
  for (const side of sides) {
16281
- const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : side === "y-" ? { x: anchorPoint.x, y: anchorPoint.y - offset } : side === "x-" ? { x: anchorPoint.x - offset, y: anchorPoint.y } : { x: anchorPoint.x + offset, y: anchorPoint.y };
16282
- const halfAlong = width / 2;
16283
- const halfAcross = height / 2;
16284
- const bounds = axis === "x" ? {
16285
- minX: center.x - halfAlong,
16286
- maxX: center.x + halfAlong,
16287
- minY: center.y - halfAcross,
16288
- maxY: center.y + halfAcross
16289
- } : {
16290
- minX: center.x - halfAcross,
16291
- maxX: center.x + halfAcross,
16292
- minY: center.y - halfAlong,
16293
- maxY: center.y + halfAlong
16294
- };
16295
- if (this.isObstructed(bounds, {
16296
- ownGlobalConnNetId: globalConnNetId,
16297
- ignoredTraceIds
16298
- })) {
16299
- continue;
16300
- }
16301
- return {
16302
- globalConnNetId,
16303
- netId: connection.netId,
16304
- netLabelText,
16305
- pinIds: [pinId],
16306
- stubTracePath: [start, end],
16307
- axis,
16791
+ for (const offset of this.getInlineOffsets(
16308
16792
  anchorPoint,
16309
- center,
16793
+ axis,
16794
+ side,
16310
16795
  width,
16311
16796
  height,
16312
- side
16313
- };
16797
+ obstacles?.traces ?? this.traces,
16798
+ ignoredTraceIds
16799
+ )) {
16800
+ const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : side === "y-" ? { x: anchorPoint.x, y: anchorPoint.y - offset } : side === "x-" ? { x: anchorPoint.x - offset, y: anchorPoint.y } : { x: anchorPoint.x + offset, y: anchorPoint.y };
16801
+ const halfAlong = width / 2;
16802
+ const halfAcross = height / 2;
16803
+ const bounds = axis === "x" ? {
16804
+ minX: center.x - halfAlong,
16805
+ maxX: center.x + halfAlong,
16806
+ minY: center.y - halfAcross,
16807
+ maxY: center.y + halfAcross
16808
+ } : {
16809
+ minX: center.x - halfAcross,
16810
+ maxX: center.x + halfAcross,
16811
+ minY: center.y - halfAlong,
16812
+ maxY: center.y + halfAlong
16813
+ };
16814
+ if (this.isObstructed(bounds, {
16815
+ ownGlobalConnNetId: globalConnNetId,
16816
+ ignoredTraceIds,
16817
+ obstacles
16818
+ })) {
16819
+ continue;
16820
+ }
16821
+ return {
16822
+ globalConnNetId,
16823
+ netId: connection.netId,
16824
+ netLabelText,
16825
+ pinIds: [pinId],
16826
+ stubTracePath: [start, end],
16827
+ axis,
16828
+ anchorPoint,
16829
+ center,
16830
+ width,
16831
+ height,
16832
+ side
16833
+ };
16834
+ }
16314
16835
  }
16315
16836
  return null;
16316
16837
  }
@@ -16331,39 +16852,49 @@ var InlineNetLabelSolver = class extends BaseSolver {
16331
16852
  const sides = segment.axis === "x" ? ["y+", "y-"] : ["x-", "x+"];
16332
16853
  for (const side of sides) {
16333
16854
  for (const anchorPoint of getAnchorCandidates(segment, width)) {
16334
- const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : side === "y-" ? { x: anchorPoint.x, y: anchorPoint.y - offset } : side === "x-" ? { x: anchorPoint.x - offset, y: anchorPoint.y } : { x: anchorPoint.x + offset, y: anchorPoint.y };
16335
- const halfAlong = width / 2;
16336
- const halfAcross = height / 2;
16337
- const bounds = segment.axis === "x" ? {
16338
- minX: center.x - halfAlong,
16339
- maxX: center.x + halfAlong,
16340
- minY: center.y - halfAcross,
16341
- maxY: center.y + halfAcross
16342
- } : {
16343
- minX: center.x - halfAcross,
16344
- maxX: center.x + halfAcross,
16345
- minY: center.y - halfAlong,
16346
- maxY: center.y + halfAlong
16347
- };
16348
- if (this.isObstructed(bounds, {
16349
- ownTrace: trace,
16350
- ownGlobalConnNetId: trace.globalConnNetId,
16351
- ignoredTraceIds
16352
- }))
16353
- continue;
16354
- return {
16355
- globalConnNetId: trace.globalConnNetId,
16356
- netId: connection.netId,
16357
- netLabelText,
16358
- mspPairId: trace.mspPairId,
16359
- pinIds: [...pinIds],
16360
- axis: segment.axis,
16855
+ for (const offset2 of this.getInlineOffsets(
16361
16856
  anchorPoint,
16362
- center,
16857
+ segment.axis,
16858
+ side,
16363
16859
  width,
16364
16860
  height,
16365
- side
16366
- };
16861
+ this.traces,
16862
+ ignoredTraceIds
16863
+ )) {
16864
+ const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset2 } : side === "y-" ? { x: anchorPoint.x, y: anchorPoint.y - offset2 } : side === "x-" ? { x: anchorPoint.x - offset2, y: anchorPoint.y } : { x: anchorPoint.x + offset2, y: anchorPoint.y };
16865
+ const halfAlong = width / 2;
16866
+ const halfAcross = height / 2;
16867
+ const bounds = segment.axis === "x" ? {
16868
+ minX: center.x - halfAlong,
16869
+ maxX: center.x + halfAlong,
16870
+ minY: center.y - halfAcross,
16871
+ maxY: center.y + halfAcross
16872
+ } : {
16873
+ minX: center.x - halfAcross,
16874
+ maxX: center.x + halfAcross,
16875
+ minY: center.y - halfAlong,
16876
+ maxY: center.y + halfAlong
16877
+ };
16878
+ if (this.isObstructed(bounds, {
16879
+ ownTrace: trace,
16880
+ ownGlobalConnNetId: trace.globalConnNetId,
16881
+ ignoredTraceIds
16882
+ }))
16883
+ continue;
16884
+ return {
16885
+ globalConnNetId: trace.globalConnNetId,
16886
+ netId: connection.netId,
16887
+ netLabelText,
16888
+ mspPairId: trace.mspPairId,
16889
+ pinIds: [...pinIds],
16890
+ axis: segment.axis,
16891
+ anchorPoint,
16892
+ center,
16893
+ width,
16894
+ height,
16895
+ side
16896
+ };
16897
+ }
16367
16898
  }
16368
16899
  }
16369
16900
  }
@@ -16473,7 +17004,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
16473
17004
  isObstructed(bounds, {
16474
17005
  ownTrace,
16475
17006
  ownGlobalConnNetId,
16476
- ignoredTraceIds = /* @__PURE__ */ new Set()
17007
+ ignoredTraceIds = /* @__PURE__ */ new Set(),
17008
+ obstacles
16477
17009
  }) {
16478
17010
  for (const chip of this.inputProblem.chips) {
16479
17011
  const chipBounds = {
@@ -16487,82 +17019,101 @@ var InlineNetLabelSolver = class extends BaseSolver {
16487
17019
  for (const textBox of this.inputProblem.textBoxes ?? []) {
16488
17020
  if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
16489
17021
  }
16490
- for (const trace of this.traces) {
17022
+ for (const trace of obstacles?.traces ?? this.traces) {
16491
17023
  if (ignoredTraceIds.has(trace.mspPairId)) continue;
16492
17024
  if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
16493
17025
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
16494
17026
  if (doesPathIntersectBounds3(trace.tracePath, bounds)) return true;
16495
17027
  }
17028
+ if (obstacles?.anchored.some(
17029
+ (label) => boundsOverlap(bounds, getAnchoredNetLabelRenderedBounds(label))
17030
+ ))
17031
+ return true;
17032
+ if (obstacles?.inline.some(
17033
+ (label) => boundsOverlap(bounds, getInlinePlacementBounds(label)) || label.stubTracePath && label.globalConnNetId !== ownGlobalConnNetId && doesPathIntersectBounds3(label.stubTracePath, bounds)
17034
+ ))
17035
+ return true;
16496
17036
  return false;
16497
17037
  }
16498
- /**
16499
- * Net label placements superseded by an inline label. A net gets one label or
16500
- * the other, never both.
16501
- */
16502
- getSupersededNetLabelKeys(placements = this.inlineNetLabelPlacements) {
16503
- const keys = /* @__PURE__ */ new Set();
16504
- for (const placement of placements) {
16505
- keys.add(placement.globalConnNetId);
16506
- }
16507
- return keys;
16508
- }
16509
- getOutputTraces(supersededNetLabelKeys, supersededTraceIds) {
17038
+ getOutputTraces(placements) {
17039
+ const activeKeys = new Set(placements.map(getInlinePlacementKey));
17040
+ const supersededTraceIds = new Set(
17041
+ this.inlineConversions.filter(
17042
+ (conversion) => conversion.placements.every(
17043
+ (placement) => activeKeys.has(getInlinePlacementKey(placement))
17044
+ )
17045
+ ).flatMap((conversion) => [...conversion.supersededTraceIds])
17046
+ );
16510
17047
  return this.traces.filter(
16511
- (trace) => !supersededTraceIds.has(trace.mspPairId) && !(supersededNetLabelKeys.has(trace.globalConnNetId) && isGeneratedAnchoredLabelConnector(trace))
17048
+ (trace) => !supersededTraceIds.has(trace.mspPairId) && !(this.netLabelConnectorTraceIds.has(trace.mspPairId) && isLabelRepresentedInline(trace, placements))
16512
17049
  );
16513
17050
  }
16514
- getInlineGlobalsOverlappingAnchoredLabels({
17051
+ getBlockedInlinePlacements({
16515
17052
  inlineNetLabelPlacements,
16516
- anchoredNetLabelPlacements
17053
+ anchoredNetLabelPlacements,
17054
+ traces
16517
17055
  }) {
16518
- const blockedGlobalConnNetIds = /* @__PURE__ */ new Set();
16519
- for (const inlinePlacement of inlineNetLabelPlacements) {
16520
- const inlineBounds = getInlinePlacementBounds(inlinePlacement);
16521
- for (const anchoredPlacement of anchoredNetLabelPlacements) {
16522
- if (anchoredPlacement.globalConnNetId === inlinePlacement.globalConnNetId) {
16523
- continue;
16524
- }
16525
- const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchoredPlacement);
16526
- if (boundsOverlap(inlineBounds, anchoredBounds) || inlinePlacement.stubTracePath && doesPathIntersectBounds3(
16527
- inlinePlacement.stubTracePath,
16528
- anchoredBounds
16529
- )) {
16530
- blockedGlobalConnNetIds.add(inlinePlacement.globalConnNetId);
16531
- break;
16532
- }
17056
+ const blockedKeys = /* @__PURE__ */ new Set();
17057
+ for (const inline of inlineNetLabelPlacements) {
17058
+ const bounds = getInlinePlacementBounds(inline);
17059
+ const blockedByLabel = anchoredNetLabelPlacements.some((anchored) => {
17060
+ const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchored);
17061
+ return boundsOverlap(bounds, anchoredBounds) || inline.stubTracePath && doesPathIntersectBounds3(inline.stubTracePath, anchoredBounds);
17062
+ });
17063
+ const blockedByTrace = traces.some(
17064
+ (trace) => trace.globalConnNetId !== inline.globalConnNetId && (doesPathIntersectBounds3(trace.tracePath, bounds) || inline.stubTracePath && terminalStubIntersectsTrace(inline.stubTracePath, trace))
17065
+ );
17066
+ const blockedByInlineWire = inlineNetLabelPlacements.some(
17067
+ (other) => other !== inline && other.globalConnNetId !== inline.globalConnNetId && other.stubTracePath && doesPathIntersectBounds3(other.stubTracePath, bounds)
17068
+ );
17069
+ const blockedByInlineText = inlineNetLabelPlacements.some(
17070
+ (other) => other !== inline && boundsOverlap(bounds, getInlinePlacementBounds(other))
17071
+ );
17072
+ if (blockedByLabel || blockedByTrace || blockedByInlineWire || blockedByInlineText)
17073
+ blockedKeys.add(getInlinePlacementKey(inline));
17074
+ }
17075
+ for (const conversion of this.inlineConversions) {
17076
+ if (conversion.placements.some(
17077
+ (placement) => blockedKeys.has(getInlinePlacementKey(placement))
17078
+ )) {
17079
+ for (const placement of conversion.placements)
17080
+ blockedKeys.add(getInlinePlacementKey(placement));
16533
17081
  }
16534
17082
  }
16535
- return blockedGlobalConnNetIds;
17083
+ return blockedKeys;
16536
17084
  }
16537
17085
  buildPostProcessedOutput() {
16538
17086
  let activeInlinePlacements = this.inlineNetLabelPlacements;
16539
17087
  while (true) {
16540
- const supersededNetLabelKeys = this.getSupersededNetLabelKeys(
16541
- activeInlinePlacements
16542
- );
16543
- const supersededTraceIds = new Set(
16544
- [...supersededNetLabelKeys].flatMap((globalConnNetId) => [
16545
- ...this.supersededTraceIdsByGlobalConnNetId.get(globalConnNetId) ?? []
16546
- ])
16547
- );
16548
17088
  const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
16549
- (placement) => !supersededNetLabelKeys.has(placement.globalConnNetId)
17089
+ (placement) => !isLabelRepresentedInline(placement, activeInlinePlacements)
16550
17090
  );
16551
- const outputTraces = this.getOutputTraces(
16552
- supersededNetLabelKeys,
16553
- supersededTraceIds
17091
+ const outputTraces = this.getOutputTraces(activeInlinePlacements);
17092
+ const { fixedLabels, terminalTraces } = getInlineLabelObstacles(
17093
+ this.inputProblem,
17094
+ activeInlinePlacements
16554
17095
  );
17096
+ const labelCollisionSolver = new NetLabelNetLabelCollisionSolver({
17097
+ inputProblem: this.inputProblem,
17098
+ traces: [...outputTraces, ...terminalTraces],
17099
+ netLabelPlacements: retainedNetLabelPlacements,
17100
+ fixedNetLabelPlacements: fixedLabels,
17101
+ useRenderedLabelBounds: activeInlinePlacements.length > 0
17102
+ });
17103
+ if (activeInlinePlacements.length > 0) labelCollisionSolver.solve();
16555
17104
  const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
16556
17105
  inputProblem: this.inputProblem,
16557
17106
  traces: outputTraces,
16558
- netLabelPlacements: retainedNetLabelPlacements,
16559
- inlineNetLabelPlacements: activeInlinePlacements
17107
+ netLabelPlacements: labelCollisionSolver.solved && !labelCollisionSolver.failed ? labelCollisionSolver.getOutput().netLabelPlacements : retainedNetLabelPlacements,
17108
+ inlineNetLabelPlacements: activeInlinePlacements,
17109
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
16560
17110
  });
16561
- const blockedGlobalConnNetIds = this.getInlineGlobalsOverlappingAnchoredLabels({
17111
+ let blockedPlacementKeys = this.getBlockedInlinePlacements({
16562
17112
  inlineNetLabelPlacements: activeInlinePlacements,
16563
- anchoredNetLabelPlacements: pushed.netLabelPlacements
17113
+ anchoredNetLabelPlacements: pushed.netLabelPlacements,
17114
+ traces: pushed.traces
16564
17115
  });
16565
- if (blockedGlobalConnNetIds.size > 0) {
17116
+ if (blockedPlacementKeys.size > 0) {
16566
17117
  const shiftedInlineTerminalLabels = pushInlineTerminalLabelsAwayFromAnchoredLabels({
16567
17118
  inputProblem: this.inputProblem,
16568
17119
  traces: pushed.traces,
@@ -16573,8 +17124,49 @@ var InlineNetLabelSolver = class extends BaseSolver {
16573
17124
  activeInlinePlacements = shiftedInlineTerminalLabels.inlineNetLabelPlacements;
16574
17125
  continue;
16575
17126
  }
17127
+ const { terminalTraces: terminalTraces2 } = getInlineLabelObstacles(
17128
+ this.inputProblem,
17129
+ activeInlinePlacements
17130
+ );
17131
+ const replanned = activeInlinePlacements.filter(
17132
+ (label) => !label.stubTracePath
17133
+ );
17134
+ const terminals = activeInlinePlacements.filter((label) => label.stubTracePath).sort(
17135
+ (a, b) => Number(blockedPlacementKeys.has(getInlinePlacementKey(b))) - Number(blockedPlacementKeys.has(getInlinePlacementKey(a)))
17136
+ );
17137
+ for (const placement of terminals) {
17138
+ const connection = [
17139
+ ...this.inputProblem.netConnections,
17140
+ ...this.inputProblem.directConnections
17141
+ ].find(
17142
+ (connection2) => connection2.netId === placement.netId && connection2.allowInlineNetLabel
17143
+ );
17144
+ const candidate = connection ? this.computeTerminalInlinePlacement(
17145
+ connection,
17146
+ placement.pinIds[0],
17147
+ placement.globalConnNetId,
17148
+ /* @__PURE__ */ new Set(),
17149
+ {
17150
+ traces: [...pushed.traces, ...terminalTraces2],
17151
+ anchored: pushed.netLabelPlacements,
17152
+ inline: replanned
17153
+ }
17154
+ ) : null;
17155
+ replanned.push(candidate ?? placement);
17156
+ }
17157
+ const byKey = new Map(
17158
+ replanned.map((label) => [getInlinePlacementKey(label), label])
17159
+ );
17160
+ activeInlinePlacements = activeInlinePlacements.map(
17161
+ (label) => byKey.get(getInlinePlacementKey(label)) ?? label
17162
+ );
17163
+ blockedPlacementKeys = this.getBlockedInlinePlacements({
17164
+ inlineNetLabelPlacements: activeInlinePlacements,
17165
+ anchoredNetLabelPlacements: pushed.netLabelPlacements,
17166
+ traces: pushed.traces
17167
+ });
16576
17168
  }
16577
- if (blockedGlobalConnNetIds.size === 0) {
17169
+ if (blockedPlacementKeys.size === 0) {
16578
17170
  this.inlineNetLabelPlacements = activeInlinePlacements;
16579
17171
  this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
16580
17172
  const restored = restoreReroutesAroundSupersededLabels({
@@ -16585,17 +17177,141 @@ var InlineNetLabelSolver = class extends BaseSolver {
16585
17177
  completedReroutes: this.completedReroutes
16586
17178
  });
16587
17179
  this.stats.restoredSupersededLabelRerouteCount = restored.restoredTraceCount;
16588
- return {
17180
+ const output = {
16589
17181
  traces: restored.traces,
17182
+ netLabelConnectorTraceIds: pushed.netLabelConnectorTraceIds,
16590
17183
  netLabelPlacements: pushed.netLabelPlacements,
16591
17184
  inlineNetLabelPlacements: activeInlinePlacements
16592
17185
  };
17186
+ this.inlineNetLabelPlacements = output.inlineNetLabelPlacements;
17187
+ return output;
16593
17188
  }
16594
17189
  activeInlinePlacements = activeInlinePlacements.filter(
16595
- (placement) => !blockedGlobalConnNetIds.has(placement.globalConnNetId)
17190
+ (placement) => !blockedPlacementKeys.has(getInlinePlacementKey(placement))
16596
17191
  );
16597
17192
  }
16598
17193
  }
17194
+ /** Enumerate nearby moves in the existing order; collect one proposal per step. */
17195
+ *getTraceRefinementProposals(current) {
17196
+ yield* getLocalTraceLabelShifts(this.inputProblem, current);
17197
+ for (const label of current.netLabelPlacements) {
17198
+ if (label.pinIds.length !== 1) continue;
17199
+ const connection = [
17200
+ ...this.inputProblem.netConnections,
17201
+ ...this.inputProblem.directConnections
17202
+ ].find(
17203
+ (connection2) => connection2.allowInlineNetLabel && connection2.netId === label.netId && connection2.pinIds.includes(label.pinIds[0])
17204
+ );
17205
+ if (!connection) continue;
17206
+ const placement = this.computeTerminalInlinePlacement(
17207
+ connection,
17208
+ label.pinIds[0],
17209
+ label.globalConnNetId,
17210
+ /* @__PURE__ */ new Set(),
17211
+ { traces: [], anchored: [], inline: [] }
17212
+ );
17213
+ if (placement)
17214
+ yield* getLocalTraceLabelShifts(this.inputProblem, current, [placement]);
17215
+ }
17216
+ }
17217
+ createRefinementCandidate(proposal) {
17218
+ const traceMap = new Map(
17219
+ proposal.traces.map((trace) => [trace.mspPairId, trace])
17220
+ );
17221
+ const labelKey = (label) => JSON.stringify([label.globalConnNetId, [...label.pinIds].sort()]);
17222
+ const labelMap = new Map(
17223
+ proposal.netLabelPlacements.map((label) => [labelKey(label), label])
17224
+ );
17225
+ const originalTraceIds = new Set(
17226
+ this.traces.map((trace) => trace.mspPairId)
17227
+ );
17228
+ return new _InlineNetLabelSolver({
17229
+ inputProblem: this.inputProblem,
17230
+ traces: [
17231
+ ...this.traces.map((trace) => traceMap.get(trace.mspPairId) ?? trace),
17232
+ ...proposal.traces.filter(
17233
+ (trace) => !originalTraceIds.has(trace.mspPairId)
17234
+ )
17235
+ ],
17236
+ netLabelPlacements: this.inputNetLabelPlacements.map(
17237
+ (label) => labelMap.get(labelKey(label)) ?? label
17238
+ ),
17239
+ completedReroutes: this.completedReroutes,
17240
+ netLabelConnectorTraceIds: proposal.netLabelConnectorTraceIds
17241
+ });
17242
+ }
17243
+ acceptRefinementCandidate(proposal, result) {
17244
+ const current = this.postProcessedOutput;
17245
+ const before = this.refinement.before;
17246
+ const after = getOutputLabelCollisions(result);
17247
+ if (after.some(
17248
+ (collision) => !before.some(
17249
+ (previous) => sameOutputLabelCollision(collision, previous)
17250
+ )
17251
+ ))
17252
+ return false;
17253
+ const movedTraceIds = new Set(
17254
+ proposal.traces.filter((trace, index) => trace !== current.traces[index]).map((trace) => trace.mspPairId)
17255
+ );
17256
+ if (after.some(
17257
+ (collision) => collision.kind === "trace-label" && collision.trace.kind === "routed" && movedTraceIds.has(collision.trace.id)
17258
+ ))
17259
+ return false;
17260
+ if (after.length >= before.length && result.inlineNetLabelPlacements.length <= current.inlineNetLabelPlacements.length)
17261
+ return false;
17262
+ this.postProcessedOutput = result;
17263
+ this.inlineNetLabelPlacements = result.inlineNetLabelPlacements;
17264
+ return true;
17265
+ }
17266
+ stepTraceRefinement() {
17267
+ if (this.activeSubSolver) {
17268
+ this.activeSubSolver.step();
17269
+ if (!this.activeSubSolver.solved && !this.activeSubSolver.failed) return;
17270
+ if (this.activeSubSolver.solved && !this.activeSubSolver.failed && this.acceptRefinementCandidate(
17271
+ this.currentProposal,
17272
+ this.activeSubSolver.getOutput()
17273
+ )) {
17274
+ this.refinement = void 0;
17275
+ }
17276
+ this.activeSubSolver = null;
17277
+ this.currentProposal = void 0;
17278
+ return;
17279
+ }
17280
+ const current = this.postProcessedOutput;
17281
+ if (!this.refinement) {
17282
+ this.refinement = {
17283
+ proposals: this.getTraceRefinementProposals(current),
17284
+ queue: [],
17285
+ collecting: true,
17286
+ before: getOutputLabelCollisions(current)
17287
+ };
17288
+ }
17289
+ const refinement = this.refinement;
17290
+ if (refinement.collecting) {
17291
+ const next2 = refinement.proposals.next();
17292
+ if (!next2.done) {
17293
+ const distance7 = next2.value.traces.reduce(
17294
+ (sum, trace, index) => sum + trace.tracePath.reduce((distance8, point, pointIndex) => {
17295
+ const previous = current.traces[index].tracePath[pointIndex];
17296
+ return distance8 + Math.abs(point.x - previous.x) + Math.abs(point.y - previous.y);
17297
+ }, 0),
17298
+ 0
17299
+ );
17300
+ refinement.queue.push({ output: next2.value, distance: distance7 });
17301
+ } else {
17302
+ refinement.queue.sort((a, b) => a.distance - b.distance);
17303
+ refinement.collecting = false;
17304
+ }
17305
+ return;
17306
+ }
17307
+ const next = refinement.queue.shift();
17308
+ if (!next) {
17309
+ this.refinementComplete = true;
17310
+ return;
17311
+ }
17312
+ this.currentProposal = next.output;
17313
+ this.activeSubSolver = this.createRefinementCandidate(next.output);
17314
+ }
16599
17315
  getOutput() {
16600
17316
  if (this.postProcessedOutput) return this.postProcessedOutput;
16601
17317
  return this.buildPostProcessedOutput();
@@ -16774,12 +17490,12 @@ var getCollisionLimitedTerminalStubEnd = ({
16774
17490
  const maxAcross = Math.max(startAcross, endAcross);
16775
17491
  let collisionDistance;
16776
17492
  const isCollinear = Math.abs(startAcross) <= GEOMETRY_EPSILON && Math.abs(endAcross) <= GEOMETRY_EPSILON;
16777
- if (isCollinear && maxAlong >= -GEOMETRY_EPSILON && minAlong <= intendedLength + GEOMETRY_EPSILON) {
17493
+ if (isCollinear && maxAlong >= -GEOMETRY_EPSILON && minAlong <= intendedLength + INLINE_NET_LABEL_STUB_TRACE_CLEARANCE + GEOMETRY_EPSILON) {
16778
17494
  collisionDistance = Math.max(0, minAlong);
16779
17495
  } else {
16780
17496
  const crossesStubAxis = minAcross <= GEOMETRY_EPSILON && maxAcross >= -GEOMETRY_EPSILON;
16781
17497
  const perpendicularAlong = (startAlong + endAlong) / 2;
16782
- if (crossesStubAxis && Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON && perpendicularAlong >= -GEOMETRY_EPSILON && perpendicularAlong <= intendedLength + GEOMETRY_EPSILON) {
17498
+ if (crossesStubAxis && Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON && perpendicularAlong >= -GEOMETRY_EPSILON && perpendicularAlong <= intendedLength + INLINE_NET_LABEL_STUB_TRACE_CLEARANCE + GEOMETRY_EPSILON) {
16783
17499
  collisionDistance = Math.max(0, perpendicularAlong);
16784
17500
  }
16785
17501
  }
@@ -16889,12 +17605,12 @@ var getTraceRecoveryConnectivityMaps = (inputProblem) => {
16889
17605
  };
16890
17606
 
16891
17607
  // lib/solvers/NetLabelToTraceSolver/reduceTraceCrossings.ts
16892
- var EPS16 = 1e-9;
16893
- var isHorizontal4 = (start, end) => Math.abs(start.y - end.y) < EPS16;
16894
- var isVertical5 = (start, end) => Math.abs(start.x - end.x) < EPS16;
17608
+ var EPS17 = 1e-9;
17609
+ var isHorizontal4 = (start, end) => Math.abs(start.y - end.y) < EPS17;
17610
+ var isVertical5 = (start, end) => Math.abs(start.x - end.x) < EPS17;
16895
17611
  var compactConsecutivePoints = (path) => path.filter((point, index) => {
16896
17612
  const previousPoint = path[index - 1];
16897
- return !previousPoint || Math.abs(point.x - previousPoint.x) >= EPS16 || Math.abs(point.y - previousPoint.y) >= EPS16;
17613
+ return !previousPoint || Math.abs(point.x - previousPoint.x) >= EPS17 || Math.abs(point.y - previousPoint.y) >= EPS17;
16898
17614
  });
16899
17615
  var isValidOrthogonalPath = (path) => {
16900
17616
  if (path.length < 2 || !path.every((point, index) => {
@@ -16932,7 +17648,7 @@ var getEndpointAlignedSegmentCandidates = (tracePath) => {
16932
17648
  }
16933
17649
  const alignedCoordinates = segmentIsVertical ? [previousPoint.x, nextPoint.x] : [previousPoint.y, nextPoint.y];
16934
17650
  for (const alignedCoordinate of alignedCoordinates) {
16935
- if (Math.abs(alignedCoordinate - (segmentIsVertical ? start.x : start.y)) < EPS16) {
17651
+ if (Math.abs(alignedCoordinate - (segmentIsVertical ? start.x : start.y)) < EPS17) {
16936
17652
  continue;
16937
17653
  }
16938
17654
  const candidate = tracePath.map((point) => ({ ...point }));
@@ -16990,7 +17706,6 @@ var reduceTraceCrossings = ({
16990
17706
  };
16991
17707
 
16992
17708
  // lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts
16993
- var AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-";
16994
17709
  var RECOVERED_TRACE_PREFIX = "net-label-to-trace-";
16995
17710
  var MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET = 0.05;
16996
17711
  var MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET = 0.25;
@@ -17030,6 +17745,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17030
17745
  inputProblem;
17031
17746
  outputTraces;
17032
17747
  outputNetLabelPlacements;
17748
+ recoveredTraceIds = /* @__PURE__ */ new Set();
17033
17749
  chipMap;
17034
17750
  pinMap;
17035
17751
  queuedCandidates;
@@ -17246,7 +17962,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17246
17962
  return candidates;
17247
17963
  }
17248
17964
  isSupersededConnectorTrace(trace, candidate) {
17249
- if (!trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_PREFIX)) {
17965
+ if (!this.input.netLabelConnectorTraceIds?.has(trace.mspPairId)) {
17250
17966
  return false;
17251
17967
  }
17252
17968
  if (trace.pinIds.length !== 1) return false;
@@ -17296,6 +18012,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17296
18012
  mspConnectionPairIds: [mspPairId],
17297
18013
  pinIds: [firstPin.pinId, secondPin.pinId]
17298
18014
  };
18015
+ this.recoveredTraceIds.add(mspPairId);
17299
18016
  this.outputTraces = [...retainedTraces, recoveredTrace];
17300
18017
  if (candidate.recoveryMode !== "routed_components") {
17301
18018
  this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
@@ -17355,7 +18072,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17355
18072
  ...this.getOutput()
17356
18073
  });
17357
18074
  for (const trace of this.outputTraces) {
17358
- if (!trace.mspPairId.startsWith(RECOVERED_TRACE_PREFIX)) continue;
18075
+ if (!this.recoveredTraceIds.has(trace.mspPairId)) continue;
17359
18076
  graphics.lines.push({
17360
18077
  points: trace.tracePath,
17361
18078
  strokeColor: "green",
@@ -17821,7 +18538,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
17821
18538
  inputProblem: instance.inputProblem,
17822
18539
  traces: junctionOutput.traces,
17823
18540
  netLabelPlacements: junctionOutput.netLabelPlacements,
17824
- completedReroutes
18541
+ completedReroutes,
18542
+ resolveTraceCollisions: true,
18543
+ netLabelConnectorTraceIds: instance.availableNetOrientationSolver.netLabelConnectorTraceIds
17825
18544
  }
17826
18545
  ];
17827
18546
  }