@tscircuit/schematic-trace-solver 0.0.187 → 0.0.189

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 (60) hide show
  1. package/dist/index.d.ts +42 -23
  2. package/dist/index.js +1053 -316
  3. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +612 -230
  4. package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +2 -6
  5. package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +13 -0
  6. package/lib/solvers/InlineNetLabelSolver/getInlineLabelObstacles.ts +64 -0
  7. package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +407 -0
  8. package/lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions.ts +145 -0
  9. package/lib/solvers/InlineNetLabelSolver/isLabelRepresentedInline.ts +15 -0
  10. package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +61 -31
  11. package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +94 -16
  12. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +4 -3
  13. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +7 -7
  14. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +3 -0
  15. package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +5 -1
  16. package/lib/types/InputProblem.ts +2 -2
  17. package/package.json +1 -1
  18. package/site/SchematicTracePipelineSolver/repro-robot-controller-imu-tof.page.tsx +7 -0
  19. package/site/bug-reports/bug-report-20260907T110144Z.page.tsx +4 -0
  20. package/site/bug-reports/bug-report-20260907T145640Z.page.tsx +4 -0
  21. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  22. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +2 -2
  23. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +18 -18
  24. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
  25. package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +21 -0
  26. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +5 -38
  27. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +3 -9
  28. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +18 -30
  29. package/tests/bug-reports/bug-report-20260901T055358Z/bug-report-20260901T055358Z.test.ts +33 -0
  30. package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +282 -0
  31. package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.json +1073 -0
  32. package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +12 -0
  33. package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +1107 -0
  34. package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.json +3563 -0
  35. package/tests/bug-reports/bug-report-20260907T145640Z/bug-report-20260907T145640Z.test.ts +117 -0
  36. package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +4 -4
  37. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
  38. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +20 -20
  39. package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +37 -55
  40. package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +44 -68
  41. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +3 -9
  42. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +199 -0
  43. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.source.svg +33 -0
  44. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +50 -74
  45. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  46. package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +103 -0
  47. package/tests/repros/assets/repro-robot-controller-imu-tof.input.json +540 -0
  48. package/tests/repros/assets/rp2040-robot-controller.input.json +349 -0
  49. package/tests/repros/repro-battery-management-bq24073.test.ts +13 -1
  50. package/tests/repros/repro-hub-usb-sheet.test.ts +25 -0
  51. package/tests/repros/repro-robot-controller-imu-tof.test.ts +83 -0
  52. package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +59 -13
  53. package/tests/repros/rp2040-robot-controller-disconnected-gnd.test.ts +59 -0
  54. package/tests/solvers/InlineNetLabelSolver/get-anchored-net-label-rendered-bounds.test.ts +24 -0
  55. package/tests/solvers/InlineNetLabelSolver/local-trace-label-shifts.test.ts +296 -0
  56. package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +7 -4
  57. package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +2 -1
  58. package/tests/solvers/InlineNetLabelSolver/shared-direct-and-named-connection.test.ts +62 -0
  59. package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +9 -3
  60. package/tests/solvers/TraceCleanupSolver/perpendicular-detours-preserve-endpoints.test.ts +60 -0
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 = {}) => {
@@ -7005,13 +7005,15 @@ var generatePerpendicularTraceDetours = ({
7005
7005
  bounds2[highBound] + clearance
7006
7006
  ])
7007
7007
  ].filter((coordinate) => coordinate !== void 0);
7008
+ const remainingPath = path.slice(index + 2);
7009
+ if (remainingPath.length === 0) remainingPath.push(end);
7008
7010
  return [...new Set(detourCoordinates)].map(
7009
7011
  (detour) => simplifyPath([
7010
7012
  ...path.slice(0, index + 1),
7011
7013
  { ...start, [movingAxis]: gate },
7012
7014
  { ...start, [movingAxis]: gate, [detourAxis]: detour },
7013
7015
  { ...end, [detourAxis]: detour },
7014
- ...path.slice(index + 2)
7016
+ ...remainingPath
7015
7017
  ])
7016
7018
  );
7017
7019
  };
@@ -12400,6 +12402,33 @@ globalConnNetId: ${label.globalConnNetId}`
12400
12402
  }
12401
12403
  };
12402
12404
 
12405
+ // lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts
12406
+ var getAnchoredNetLabelRenderedBounds = (placement) => {
12407
+ if (placement.orientation !== "x-" && placement.orientation !== "x+") {
12408
+ if (placement.netLabelText && placement.width > placement.height) {
12409
+ const width2 = placement.height;
12410
+ const height2 = placement.width;
12411
+ const center2 = getCenterFromAnchor(
12412
+ placement.anchorPoint,
12413
+ placement.orientation,
12414
+ width2,
12415
+ height2
12416
+ );
12417
+ return getRectBounds(center2, width2, height2);
12418
+ }
12419
+ return getRectBounds(placement.center, placement.width, placement.height);
12420
+ }
12421
+ const width = Math.max(placement.width, placement.height);
12422
+ const height = NET_LABEL_HORIZONTAL_HEIGHT;
12423
+ const center = getCenterFromAnchor(
12424
+ placement.anchorPoint,
12425
+ placement.orientation,
12426
+ width,
12427
+ height
12428
+ );
12429
+ return getRectBounds(center, width, height);
12430
+ };
12431
+
12403
12432
  // lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts
12404
12433
  var ANCHOR_TRACE_CLEARANCE = 1e-4;
12405
12434
  var SEGMENT_PARALLEL_EPS = 1e-6;
@@ -12440,6 +12469,19 @@ function sampleAnchorsAlongSegment(a, b) {
12440
12469
  return anchors;
12441
12470
  }
12442
12471
  var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12472
+ constructor(params) {
12473
+ super();
12474
+ this.params = params;
12475
+ this.inputProblem = params.inputProblem;
12476
+ this.traces = params.traces;
12477
+ this.netLabelPlacements = params.netLabelPlacements;
12478
+ this.outputNetLabelPlacements = [...params.netLabelPlacements];
12479
+ this.chipIndex = params.inputProblem._chipObstacleSpatialIndex ?? (params.inputProblem.chips.length > 0 ? new ChipObstacleSpatialIndex(params.inputProblem.chips) : void 0);
12480
+ this.traceMap = Object.fromEntries(
12481
+ params.traces.map((t) => [t.mspPairId, t])
12482
+ );
12483
+ }
12484
+ params;
12443
12485
  inputProblem;
12444
12486
  traces;
12445
12487
  netLabelPlacements;
@@ -12453,40 +12495,37 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12453
12495
  labelsToTry = [];
12454
12496
  candidateQueue = [];
12455
12497
  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
12498
  getConstructorParams() {
12468
12499
  return {
12469
12500
  inputProblem: this.inputProblem,
12470
12501
  traces: this.traces,
12471
- netLabelPlacements: this.netLabelPlacements
12502
+ netLabelPlacements: this.netLabelPlacements,
12503
+ fixedNetLabelPlacements: this.params.fixedNetLabelPlacements,
12504
+ useRenderedLabelBounds: this.params.useRenderedLabelBounds
12472
12505
  };
12473
12506
  }
12474
12507
  getOutput() {
12475
12508
  return { netLabelPlacements: this.outputNetLabelPlacements };
12476
12509
  }
12477
12510
  labelBounds(label) {
12478
- return getRectBounds(label.center, label.width, label.height);
12511
+ if (!this.params.useRenderedLabelBounds || this.params.fixedNetLabelPlacements?.includes(label))
12512
+ return getRectBounds(label.center, label.width, label.height);
12513
+ return getAnchoredNetLabelRenderedBounds(label);
12479
12514
  }
12480
12515
  collisionKey(a, b) {
12481
12516
  return [a.globalConnNetId, b.globalConnNetId].sort().join("::");
12482
12517
  }
12483
12518
  findNextCollidingPair() {
12484
- const labels = this.outputNetLabelPlacements;
12485
- for (let i = 0; i < labels.length; i++) {
12519
+ const labels = [
12520
+ ...this.outputNetLabelPlacements,
12521
+ ...this.params.fixedNetLabelPlacements ?? []
12522
+ ];
12523
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
12486
12524
  for (let j = i + 1; j < labels.length; j++) {
12487
12525
  const a = labels[i];
12488
12526
  const b = labels[j];
12489
- if (a.globalConnNetId === b.globalConnNetId) continue;
12527
+ if (a.globalConnNetId === b.globalConnNetId && !this.params.fixedNetLabelPlacements?.includes(b))
12528
+ continue;
12490
12529
  if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue;
12491
12530
  if (boundsOverlap2(this.labelBounds(a), this.labelBounds(b)))
12492
12531
  return [a, b];
@@ -12526,14 +12565,23 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12526
12565
  center,
12527
12566
  width,
12528
12567
  height,
12529
- bounds: getRectBounds(center, width, height),
12568
+ bounds: this.params.useRenderedLabelBounds ? getAnchoredNetLabelRenderedBounds({
12569
+ ...label,
12570
+ orientation,
12571
+ anchorPoint: anchor,
12572
+ center,
12573
+ width,
12574
+ height
12575
+ }) : getRectBounds(center, width, height),
12530
12576
  hostPairId,
12531
12577
  hostSegIndex,
12532
12578
  status: null
12533
12579
  };
12534
12580
  };
12535
- const isPortOnly = label.mspConnectionPairIds.length === 0;
12536
- if (isPortOnly) {
12581
+ const isAnchoredOnConnector = this.traces.some(
12582
+ (trace) => this.params.netLabelConnectorTraceIds?.has(trace.mspPairId) && trace.globalConnNetId === label.globalConnNetId && tracePathContainsPoint(trace.tracePath, label.anchorPoint)
12583
+ );
12584
+ if (label.mspConnectionPairIds.length === 0 || isAnchoredOnConnector) {
12537
12585
  const allOrientations = ["x+", "x-", "y+", "y-"];
12538
12586
  const orderedOrientations = [
12539
12587
  label.orientation,
@@ -12578,7 +12626,7 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12578
12626
  }
12579
12627
  checkCandidate(candidate, movingLabelNetId, obstacleLabels) {
12580
12628
  const { bounds, hostPairId, hostSegIndex } = candidate;
12581
- if (this.chipIndex.getChipsInBounds(bounds).length > 0)
12629
+ if ((this.chipIndex?.getChipsInBounds(bounds).length ?? 0) > 0)
12582
12630
  return "chip-collision";
12583
12631
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
12584
12632
  return "text-collision";
@@ -12586,7 +12634,8 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12586
12634
  return "trace-collision";
12587
12635
  }
12588
12636
  for (const obstacle of obstacleLabels) {
12589
- if (obstacle.globalConnNetId === movingLabelNetId) continue;
12637
+ if (obstacle.globalConnNetId === movingLabelNetId && !this.params.fixedNetLabelPlacements?.includes(obstacle))
12638
+ continue;
12590
12639
  if (boundsOverlap2(bounds, this.labelBounds(obstacle)))
12591
12640
  return "label-collision";
12592
12641
  }
@@ -12598,6 +12647,27 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12598
12647
  this.candidateIndex = 0;
12599
12648
  this.candidateResults = [];
12600
12649
  }
12650
+ /** Reuse the normal anchor search for a local trace-clearance proposal. */
12651
+ getNearbyValidPlacements(label, maxDistance) {
12652
+ const distance7 = (candidate) => Math.hypot(
12653
+ candidate.anchor.x - label.anchorPoint.x,
12654
+ candidate.anchor.y - label.anchorPoint.y
12655
+ );
12656
+ const obstacles = [
12657
+ ...this.outputNetLabelPlacements.filter((other) => other !== label),
12658
+ ...this.params.fixedNetLabelPlacements ?? []
12659
+ ];
12660
+ return this.buildCandidatesForLabel(label).filter(
12661
+ (candidate) => distance7(candidate) <= maxDistance && this.checkCandidate(candidate, label.globalConnNetId, obstacles) === "ok"
12662
+ ).sort((a, b) => distance7(a) - distance7(b)).map((candidate) => ({
12663
+ ...label,
12664
+ orientation: candidate.orientation,
12665
+ anchorPoint: candidate.anchor,
12666
+ center: candidate.center,
12667
+ width: candidate.width,
12668
+ height: candidate.height
12669
+ }));
12670
+ }
12601
12671
  clearActiveSearch() {
12602
12672
  this.currentCollision = null;
12603
12673
  this.currentLabelToMove = null;
@@ -12614,7 +12684,9 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12614
12684
  return;
12615
12685
  }
12616
12686
  this.currentCollision = pair;
12617
- this.labelsToTry = [pair[1], pair[0]];
12687
+ this.labelsToTry = [pair[1], pair[0]].filter(
12688
+ (label) => !(this.params.fixedNetLabelPlacements ?? []).includes(label)
12689
+ );
12618
12690
  this.beginSearchForLabel(this.labelsToTry.shift());
12619
12691
  return;
12620
12692
  }
@@ -12638,9 +12710,10 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12638
12710
  fixedLabel = labelB;
12639
12711
  }
12640
12712
  const obstacleLabels = [
12641
- ...this.outputNetLabelPlacements.filter(
12642
- (l) => l !== labelA && l !== labelB
12643
- ),
12713
+ ...[
12714
+ ...this.outputNetLabelPlacements,
12715
+ ...this.params.fixedNetLabelPlacements ?? []
12716
+ ].filter((l) => l !== labelA && l !== labelB),
12644
12717
  fixedLabel
12645
12718
  ];
12646
12719
  const status = this.checkCandidate(
@@ -13601,8 +13674,10 @@ var getHorizontalPinApproachPoint = ({
13601
13674
  return approachesFromFacingSide ? approachPoint : null;
13602
13675
  };
13603
13676
  var getAttachedLabelIndexes = (trace, netLabelPlacements) => netLabelPlacements.flatMap((label, index) => {
13604
- const labelOwnsTrace = label.mspConnectionPairIds.includes(trace.mspPairId) || label.mspConnectionPairIds.length === 0 && label.globalConnNetId === trace.globalConnNetId;
13605
- return labelOwnsTrace && tracePathContainsPoint(trace.tracePath, label.anchorPoint) ? [index] : [];
13677
+ if (isLabelAttachedToTrace(label, trace) && tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
13678
+ return [index];
13679
+ }
13680
+ return [];
13606
13681
  });
13607
13682
  var moveAttachedLabels = ({
13608
13683
  trace,
@@ -14084,7 +14159,7 @@ var candidateIsClear = ({
14084
14159
  }
14085
14160
  for (const otherTrace of otherNetTraces) {
14086
14161
  const countIntersections = (path) => {
14087
- const intersections = /* @__PURE__ */ new Set();
14162
+ const intersections2 = /* @__PURE__ */ new Set();
14088
14163
  for (let i = 1; i < path.length; i++) {
14089
14164
  for (let j = 1; j < otherTrace.tracePath.length; j++) {
14090
14165
  const point = getSegmentIntersection3(
@@ -14094,10 +14169,10 @@ var candidateIsClear = ({
14094
14169
  otherTrace.tracePath[j]
14095
14170
  );
14096
14171
  if (point)
14097
- intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
14172
+ intersections2.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
14098
14173
  }
14099
14174
  }
14100
- return intersections.size;
14175
+ return intersections2.size;
14101
14176
  };
14102
14177
  if (countIntersections(candidateTrace.tracePath) > countIntersections(originalTrace.tracePath)) {
14103
14178
  return false;
@@ -15027,6 +15102,422 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
15027
15102
  }
15028
15103
  };
15029
15104
 
15105
+ // lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts
15106
+ import { getSegmentIntersection as getSegmentIntersection5 } from "@tscircuit/math-utils/line-intersections";
15107
+
15108
+ // lib/solvers/InlineNetLabelSolver/getInlineLabelObstacles.ts
15109
+ var getInlineTerminalBounds = (label) => {
15110
+ if (!label.stubTracePath) return null;
15111
+ const epsilon = 1e-6;
15112
+ return {
15113
+ minX: Math.min(...label.stubTracePath.map((p) => p.x)) - epsilon,
15114
+ maxX: Math.max(...label.stubTracePath.map((p) => p.x)) + epsilon,
15115
+ minY: Math.min(...label.stubTracePath.map((p) => p.y)) - epsilon,
15116
+ maxY: Math.max(...label.stubTracePath.map((p) => p.y)) + epsilon
15117
+ };
15118
+ };
15119
+ var getInlineLabelObstacles = (inputProblem, placements) => {
15120
+ const fixedLabels = placements.map((label) => ({
15121
+ globalConnNetId: label.globalConnNetId,
15122
+ netId: label.netId,
15123
+ pinIds: label.pinIds,
15124
+ mspConnectionPairIds: [],
15125
+ anchorPoint: label.anchorPoint,
15126
+ orientation: "y+",
15127
+ center: label.center,
15128
+ width: label.axis === "x" ? label.width : label.height,
15129
+ height: label.axis === "x" ? label.height : label.width
15130
+ }));
15131
+ const terminalTraces = placements.flatMap((label) => {
15132
+ if (!label.stubTracePath) return [];
15133
+ const chip = inputProblem.chips.find(
15134
+ (chip2) => chip2.pins.some((pin2) => label.pinIds.includes(pin2.pinId))
15135
+ );
15136
+ const pin = chip?.pins.find((pin2) => label.pinIds.includes(pin2.pinId));
15137
+ if (!chip || !pin) return [];
15138
+ const id = JSON.stringify([
15139
+ "inline-terminal",
15140
+ label.globalConnNetId,
15141
+ [...label.pinIds].sort()
15142
+ ]);
15143
+ return [
15144
+ {
15145
+ mspPairId: id,
15146
+ mspConnectionPairIds: [id],
15147
+ globalConnNetId: label.globalConnNetId,
15148
+ dcConnNetId: label.globalConnNetId,
15149
+ pinIds: label.pinIds,
15150
+ pins: [
15151
+ { ...pin, chipId: chip.chipId },
15152
+ { ...pin, ...label.stubTracePath[1], chipId: chip.chipId }
15153
+ ],
15154
+ tracePath: label.stubTracePath
15155
+ }
15156
+ ];
15157
+ });
15158
+ return { fixedLabels, terminalTraces };
15159
+ };
15160
+
15161
+ // lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts
15162
+ var EPS15 = 1e-6;
15163
+ var CLEARANCE = 0.05;
15164
+ var hits = (path, bounds) => path.slice(1).some((p, i) => segmentIntersectsRect(path[i], p, bounds));
15165
+ var same = (a, b) => Math.hypot(a.x - b.x, a.y - b.y) < EPS15;
15166
+ var translate = (p, axis, distance7) => ({
15167
+ ...p,
15168
+ [axis]: p[axis] + distance7
15169
+ });
15170
+ var intersections = (a, b) => a.slice(1).flatMap(
15171
+ (p, i) => b.slice(1).flatMap((q, j) => {
15172
+ const point = getSegmentIntersection5(a[i], p, b[j], q);
15173
+ return point ? [point] : [];
15174
+ })
15175
+ );
15176
+ function* getLocalTraceLabelShifts(inputProblem, output, pendingInlinePlacements = []) {
15177
+ const placements = [
15178
+ ...output.inlineNetLabelPlacements,
15179
+ ...pendingInlinePlacements
15180
+ ];
15181
+ const { fixedLabels, terminalTraces } = getInlineLabelObstacles(
15182
+ inputProblem,
15183
+ placements
15184
+ );
15185
+ const terminalBounds = placements.flatMap((label) => {
15186
+ const bounds = getInlineTerminalBounds(label);
15187
+ return bounds ? [{ label, bounds }] : [];
15188
+ });
15189
+ const obstacles = [
15190
+ ...inputProblem.chips.map((chip) => ({
15191
+ minX: chip.center.x - chip.width / 2,
15192
+ maxX: chip.center.x + chip.width / 2,
15193
+ minY: chip.center.y - chip.height / 2,
15194
+ maxY: chip.center.y + chip.height / 2
15195
+ })),
15196
+ ...(inputProblem.textBoxes ?? []).map((box) => getTextBoxBounds(box))
15197
+ ];
15198
+ const inlineBounds = fixedLabels.map((label) => ({
15199
+ minX: label.center.x - label.width / 2,
15200
+ maxX: label.center.x + label.width / 2,
15201
+ minY: label.center.y - label.height / 2,
15202
+ maxY: label.center.y + label.height / 2
15203
+ }));
15204
+ const pinPositions = [
15205
+ ...inputProblem.chips.flatMap((chip) => chip.pins),
15206
+ ...output.traces.flatMap((trace) => trace.pins)
15207
+ ];
15208
+ for (const trace of output.traces) {
15209
+ const path = simplifyPath(trace.tracePath);
15210
+ for (let i = 0; i < path.length - 1; i++) {
15211
+ const a = path[i], b = path[i + 1];
15212
+ const axis = Math.abs(a.x - b.x) < EPS15 ? "x" : "y";
15213
+ const along = axis === "x" ? "y" : "x";
15214
+ if (Math.abs(a[axis] - b[axis]) > EPS15 || same(a, b)) continue;
15215
+ const blocking = [
15216
+ ...output.netLabelPlacements.filter((label) => label.globalConnNetId !== trace.globalConnNetId).map(getAnchoredNetLabelRenderedBounds),
15217
+ ...inlineBounds,
15218
+ ...terminalBounds.filter(
15219
+ ({ label }) => !trace.pinIds.some((pinId) => label.pinIds.includes(pinId))
15220
+ ).map(({ bounds }) => bounds)
15221
+ ].filter((bounds) => segmentIntersectsRect(a, b, bounds));
15222
+ if (!blocking.length) continue;
15223
+ let low = Math.min(a[along], b[along]);
15224
+ let high = Math.max(a[along], b[along]);
15225
+ let expanded = true;
15226
+ while (expanded) {
15227
+ expanded = false;
15228
+ for (const other of output.traces) {
15229
+ if (other.globalConnNetId !== trace.globalConnNetId) continue;
15230
+ for (let j = 1; j < other.tracePath.length; j++) {
15231
+ const p = other.tracePath[j - 1], q = other.tracePath[j];
15232
+ if (Math.abs(p[axis] - a[axis]) > EPS15 || Math.abs(q[axis] - a[axis]) > EPS15)
15233
+ continue;
15234
+ const min = Math.min(p[along], q[along]), max = Math.max(p[along], q[along]);
15235
+ if (max < low - EPS15 || min > high + EPS15) continue;
15236
+ if (min < low || max > high) expanded = true;
15237
+ low = Math.min(low, min);
15238
+ high = Math.max(high, max);
15239
+ }
15240
+ }
15241
+ }
15242
+ const onRail = (p) => Math.abs(p[axis] - a[axis]) < EPS15 && p[along] >= low - EPS15 && p[along] <= high + EPS15;
15243
+ if (pinPositions.some(onRail)) continue;
15244
+ const minKey = axis === "x" ? "minX" : "minY";
15245
+ const maxKey = axis === "x" ? "maxX" : "maxY";
15246
+ const localWidth = Math.max(
15247
+ ...blocking.map((bounds) => bounds[maxKey] - bounds[minKey])
15248
+ );
15249
+ const railLabelBounds = output.netLabelPlacements.filter(
15250
+ (label) => label.globalConnNetId === trace.globalConnNetId && onRail(label.anchorPoint)
15251
+ ).map(getAnchoredNetLabelRenderedBounds);
15252
+ const coordinates = [
15253
+ ...new Set(
15254
+ blocking.flatMap((bounds) => [
15255
+ bounds[minKey] - CLEARANCE,
15256
+ bounds[maxKey] + CLEARANCE,
15257
+ ...railLabelBounds.filter((own) => boundsOverlap(own, bounds)).flatMap((own) => [
15258
+ a[axis] + bounds[minKey] - CLEARANCE - own[maxKey],
15259
+ a[axis] + bounds[maxKey] + CLEARANCE - own[minKey]
15260
+ ])
15261
+ ])
15262
+ )
15263
+ ].sort((x, y) => Math.abs(x - a[axis]) - Math.abs(y - a[axis]));
15264
+ for (const coordinate of coordinates) {
15265
+ const distance7 = coordinate - a[axis];
15266
+ if (Math.abs(distance7) > localWidth + 2 * CLEARANCE) continue;
15267
+ const traces = output.traces.map((other) => {
15268
+ if (other.globalConnNetId !== trace.globalConnNetId || !other.tracePath.some(onRail))
15269
+ return other;
15270
+ return {
15271
+ ...other,
15272
+ tracePath: other.tracePath.map(
15273
+ (p) => onRail(p) ? translate(p, axis, distance7) : p
15274
+ )
15275
+ };
15276
+ });
15277
+ const shiftedPaths = traces.filter((other, index) => other !== output.traces[index]).map((other) => other.tracePath);
15278
+ let labels = output.netLabelPlacements.map((label) => {
15279
+ if (label.globalConnNetId !== trace.globalConnNetId || !onRail(label.anchorPoint))
15280
+ return label;
15281
+ return {
15282
+ ...label,
15283
+ anchorPoint: translate(label.anchorPoint, axis, distance7),
15284
+ center: translate(label.center, axis, distance7)
15285
+ };
15286
+ });
15287
+ let blocked = false;
15288
+ const movedOwnLabelBounds = labels.flatMap(
15289
+ (label, index) => label !== output.netLabelPlacements[index] ? [getAnchoredNetLabelRenderedBounds(label)] : []
15290
+ );
15291
+ for (const [labelIndex, label] of labels.entries()) {
15292
+ if (label.globalConnNetId === trace.globalConnNetId) continue;
15293
+ const bounds = getAnchoredNetLabelRenderedBounds(label);
15294
+ const clearanceEdges = movedOwnLabelBounds.filter((own) => boundsOverlap(own, bounds)).map((own) => own[distance7 > 0 ? maxKey : minKey]);
15295
+ if (shiftedPaths.some((p) => hits(p, bounds)) && !output.traces.some(
15296
+ (other) => other.globalConnNetId === trace.globalConnNetId && hits(other.tracePath, bounds)
15297
+ ))
15298
+ clearanceEdges.push(coordinate);
15299
+ if (!clearanceEdges.length) continue;
15300
+ const connectorIndex = traces.findIndex(
15301
+ (candidate) => candidate.globalConnNetId === label.globalConnNetId && candidate.pinIds.length === 1 && candidate.tracePath.length === 3 && same(candidate.tracePath.at(-1), label.anchorPoint)
15302
+ );
15303
+ const connector = traces[connectorIndex];
15304
+ if (!connector) {
15305
+ const placementSolver = new NetLabelNetLabelCollisionSolver({
15306
+ inputProblem,
15307
+ traces: [...traces, ...terminalTraces],
15308
+ netLabelPlacements: labels,
15309
+ fixedNetLabelPlacements: fixedLabels,
15310
+ useRenderedLabelBounds: true
15311
+ });
15312
+ const nearby = placementSolver.getNearbyValidPlacements(
15313
+ label,
15314
+ localWidth + Math.max(label.width, label.height)
15315
+ )[0];
15316
+ if (!nearby) {
15317
+ blocked = true;
15318
+ break;
15319
+ }
15320
+ labels[labelIndex] = nearby;
15321
+ continue;
15322
+ }
15323
+ const [start, bend, end] = connector.tracePath;
15324
+ if (Math.abs(bend[axis] - end[axis]) > EPS15 || Math.abs(start[along] - bend[along]) > EPS15) {
15325
+ blocked = true;
15326
+ break;
15327
+ }
15328
+ const delta = distance7 > 0 ? Math.max(...clearanceEdges) + CLEARANCE - bounds[minKey] : Math.min(...clearanceEdges) - CLEARANCE - bounds[maxKey];
15329
+ if (Math.abs(delta) > localWidth + bounds[maxKey] - bounds[minKey] + 2 * CLEARANCE) {
15330
+ blocked = true;
15331
+ break;
15332
+ }
15333
+ const moved = {
15334
+ ...label,
15335
+ anchorPoint: translate(label.anchorPoint, axis, delta),
15336
+ center: translate(label.center, axis, delta)
15337
+ };
15338
+ labels[labelIndex] = moved;
15339
+ traces[connectorIndex] = {
15340
+ ...connector,
15341
+ tracePath: [start, translate(bend, axis, delta), moved.anchorPoint]
15342
+ };
15343
+ }
15344
+ if (blocked) continue;
15345
+ for (const [index, proposed] of traces.entries()) {
15346
+ const original = output.traces[index];
15347
+ if (proposed === original) continue;
15348
+ const candidate = proposed.tracePath;
15349
+ const previous = original.tracePath;
15350
+ if (candidate.slice(1).some(
15351
+ (p, index2) => Math.abs(p.x - candidate[index2].x) > EPS15 && Math.abs(p.y - candidate[index2].y) > EPS15
15352
+ )) {
15353
+ blocked = true;
15354
+ break;
15355
+ }
15356
+ const dot = (a2, b2, c, d) => (b2.x - a2.x) * (d.x - c.x) + (b2.y - a2.y) * (d.y - c.y);
15357
+ if (dot(previous[0], previous[1], candidate[0], candidate[1]) <= EPS15 || dot(
15358
+ previous.at(-1),
15359
+ previous.at(-2),
15360
+ candidate.at(-1),
15361
+ candidate.at(-2)
15362
+ ) <= EPS15) {
15363
+ blocked = true;
15364
+ break;
15365
+ }
15366
+ const junctions = output.traces.filter(
15367
+ (other) => other !== original && other.globalConnNetId === original.globalConnNetId
15368
+ ).flatMap((other) => [
15369
+ ...other.tracePath,
15370
+ ...intersections(previous, other.tracePath)
15371
+ ]).filter((p) => tracePathContainsPoint(previous, p)).map(
15372
+ (p) => original.globalConnNetId === trace.globalConnNetId && onRail(p) ? translate(p, axis, distance7) : p
15373
+ );
15374
+ const protectedPoints = [
15375
+ ...pinPositions.filter((p) => tracePathContainsPoint(previous, p)),
15376
+ ...junctions
15377
+ ];
15378
+ if (protectedPoints.some(
15379
+ (p) => !tracePathContainsPoint(candidate, p)
15380
+ ) || pinPositions.some((p) => {
15381
+ const clearance = {
15382
+ minX: p.x - CLEARANCE,
15383
+ maxX: p.x + CLEARANCE,
15384
+ minY: p.y - CLEARANCE,
15385
+ maxY: p.y + CLEARANCE
15386
+ };
15387
+ return hits(candidate, clearance) && !hits(previous, clearance);
15388
+ }) || obstacles.some(
15389
+ (bounds) => hits(candidate, bounds) && !hits(previous, bounds)
15390
+ )) {
15391
+ blocked = true;
15392
+ break;
15393
+ }
15394
+ for (const other of [...traces, ...terminalTraces]) {
15395
+ if (other.mspPairId === proposed.mspPairId || other.globalConnNetId === proposed.globalConnNetId)
15396
+ continue;
15397
+ const originalOther = output.traces.find((t) => t.mspPairId === other.mspPairId) ?? other;
15398
+ if (doesPathCoincideWithTraces(candidate, [other]) || intersections(candidate, other.tracePath).length > intersections(previous, originalOther.tracePath).length) {
15399
+ blocked = true;
15400
+ break;
15401
+ }
15402
+ }
15403
+ if (blocked) break;
15404
+ }
15405
+ if (blocked) continue;
15406
+ for (const [index, label] of labels.entries()) {
15407
+ if (label === output.netLabelPlacements[index]) continue;
15408
+ const bounds = getAnchoredNetLabelRenderedBounds(label);
15409
+ if (obstacles.some((obstacle) => boundsOverlap(bounds, obstacle)) || inlineBounds.some((other) => boundsOverlap(bounds, other))) {
15410
+ blocked = true;
15411
+ break;
15412
+ }
15413
+ if (labels.some(
15414
+ (other, j) => index !== j && boundsOverlap(bounds, getAnchoredNetLabelRenderedBounds(other))
15415
+ )) {
15416
+ blocked = true;
15417
+ break;
15418
+ }
15419
+ if ([...traces, ...terminalTraces].some(
15420
+ (other) => other.globalConnNetId !== label.globalConnNetId && hits(other.tracePath, bounds)
15421
+ )) {
15422
+ blocked = true;
15423
+ break;
15424
+ }
15425
+ }
15426
+ if (!blocked) yield { ...output, traces, netLabelPlacements: labels };
15427
+ }
15428
+ }
15429
+ }
15430
+ }
15431
+
15432
+ // lib/solvers/InlineNetLabelSolver/getOutputLabelCollisions.ts
15433
+ var sameLabel = (a, b) => {
15434
+ const first = [...a.pinIds].sort();
15435
+ const second = [...b.pinIds].sort();
15436
+ return a.globalConnNetId === b.globalConnNetId && first.length === second.length && first.every((pinId, index) => pinId === second[index]);
15437
+ };
15438
+ var sameOutputLabelCollision = (a, b) => {
15439
+ if (a.kind === "label-label" && b.kind === "label-label")
15440
+ 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]);
15441
+ if (a.kind !== "trace-label" || b.kind !== "trace-label") return false;
15442
+ 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);
15443
+ return sameTrace && a.target === b.target && sameLabel(a.label, b.label);
15444
+ };
15445
+ var getOutputLabelCollisions = (output) => {
15446
+ const labels = [
15447
+ ...output.netLabelPlacements,
15448
+ ...output.inlineNetLabelPlacements
15449
+ ].map((label) => ({
15450
+ label,
15451
+ bounds: "axis" in label ? {
15452
+ minX: label.center.x - (label.axis === "x" ? label.width : label.height) / 2,
15453
+ maxX: label.center.x + (label.axis === "x" ? label.width : label.height) / 2,
15454
+ minY: label.center.y - (label.axis === "x" ? label.height : label.width) / 2,
15455
+ maxY: label.center.y + (label.axis === "x" ? label.height : label.width) / 2
15456
+ } : getAnchoredNetLabelRenderedBounds(label)
15457
+ }));
15458
+ const collisions = [];
15459
+ const add = (collision) => {
15460
+ if (!collisions.some((other) => sameOutputLabelCollision(collision, other)))
15461
+ collisions.push(collision);
15462
+ };
15463
+ for (let i = 0; i < labels.length; i++)
15464
+ for (const other of labels.slice(i + 1)) {
15465
+ if (boundsOverlap(labels[i].bounds, other.bounds))
15466
+ add({ kind: "label-label", labels: [labels[i].label, other.label] });
15467
+ }
15468
+ const traces = [
15469
+ ...output.traces.map((trace) => ({
15470
+ ...trace,
15471
+ identity: { kind: "routed", id: trace.mspPairId }
15472
+ })),
15473
+ ...output.inlineNetLabelPlacements.flatMap(
15474
+ (label) => label.stubTracePath ? [
15475
+ {
15476
+ identity: { kind: "terminal", label },
15477
+ globalConnNetId: label.globalConnNetId,
15478
+ tracePath: label.stubTracePath
15479
+ }
15480
+ ] : []
15481
+ )
15482
+ ];
15483
+ for (const trace of traces)
15484
+ for (const { label, bounds } of labels) {
15485
+ if (("axis" in label || label.globalConnNetId !== trace.globalConnNetId) && trace.tracePath.slice(1).some(
15486
+ (end, index) => segmentIntersectsRect(trace.tracePath[index], end, bounds)
15487
+ ))
15488
+ add({
15489
+ kind: "trace-label",
15490
+ trace: trace.identity,
15491
+ label,
15492
+ target: "text"
15493
+ });
15494
+ }
15495
+ for (const label of output.inlineNetLabelPlacements) {
15496
+ const bounds = getInlineTerminalBounds(label);
15497
+ if (!bounds) continue;
15498
+ for (const trace of output.traces) {
15499
+ if (trace.pinIds.some((pinId) => label.pinIds.includes(pinId))) continue;
15500
+ if (trace.tracePath.slice(1).some(
15501
+ (end, index) => segmentIntersectsRect(trace.tracePath[index], end, bounds)
15502
+ ))
15503
+ add({
15504
+ kind: "trace-label",
15505
+ trace: { kind: "routed", id: trace.mspPairId },
15506
+ label,
15507
+ target: "terminal"
15508
+ });
15509
+ }
15510
+ }
15511
+ return collisions;
15512
+ };
15513
+
15514
+ // lib/solvers/InlineNetLabelSolver/isLabelRepresentedInline.ts
15515
+ var isLabelRepresentedInline = (label, inlinePlacements) => label.pinIds.length > 0 && label.pinIds.every(
15516
+ (pinId) => inlinePlacements.some(
15517
+ (inline) => inline.globalConnNetId === label.globalConnNetId && inline.pinIds.includes(pinId)
15518
+ )
15519
+ );
15520
+
15030
15521
  // lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts
15031
15522
  var getStubDirection = (path) => {
15032
15523
  const [start, end] = path;
@@ -15102,12 +15593,7 @@ var alignPortOnlyInlineNetLabelStubs = ({
15102
15593
  groups.set(groupKey, group);
15103
15594
  }
15104
15595
  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);
15596
+ const retainedAnchoredLabelBounds = netLabelPlacements.filter((placement) => !isLabelRepresentedInline(placement, placements)).map(getLabelBounds2);
15111
15597
  for (const group of groups.values()) {
15112
15598
  if (group.length < 2) continue;
15113
15599
  const targetLength = Math.max(
@@ -15216,22 +15702,6 @@ var getAxisAlignedSegments2 = (path, epsilon = 1e-6) => {
15216
15702
  return segments.sort((a, b) => b.length - a.length);
15217
15703
  };
15218
15704
 
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
15705
  // lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts
15236
15706
  var LABEL_CLEARANCE2 = 0.05;
15237
15707
  var POINT_EPSILON = 1e-6;
@@ -15279,29 +15749,37 @@ var isPointOnPath2 = (point, path) => {
15279
15749
  return false;
15280
15750
  };
15281
15751
  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
15752
+ let distance7 = 0;
15753
+ for (let iteration = 0; iteration < inlineBounds.length; iteration++) {
15754
+ const labelBounds = getAnchoredNetLabelRenderedBounds(
15755
+ moveLabel(label, label.orientation, distance7)
15286
15756
  );
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);
15757
+ const horizontal = label.orientation === "x-" || label.orientation === "x+";
15758
+ const overlapping = inlineBounds.filter(
15759
+ (bounds) => boundsOverlap(labelBounds, {
15760
+ minX: bounds.minX - (horizontal ? LABEL_CLEARANCE2 : 0),
15761
+ maxX: bounds.maxX + (horizontal ? LABEL_CLEARANCE2 : 0),
15762
+ minY: bounds.minY - (horizontal ? 0 : LABEL_CLEARANCE2),
15763
+ maxY: bounds.maxY + (horizontal ? 0 : LABEL_CLEARANCE2)
15764
+ })
15765
+ );
15766
+ if (overlapping.length === 0) break;
15767
+ switch (label.orientation) {
15768
+ case "x-":
15769
+ distance7 += labelBounds.maxX - Math.min(...overlapping.map((b) => b.minX)) + LABEL_CLEARANCE2;
15770
+ break;
15771
+ case "x+":
15772
+ distance7 += Math.max(...overlapping.map((b) => b.maxX)) - labelBounds.minX + LABEL_CLEARANCE2;
15773
+ break;
15774
+ case "y-":
15775
+ distance7 += labelBounds.maxY - Math.min(...overlapping.map((b) => b.minY)) + LABEL_CLEARANCE2;
15776
+ break;
15777
+ case "y+":
15778
+ distance7 += Math.max(...overlapping.map((b) => b.maxY)) - labelBounds.minY + LABEL_CLEARANCE2;
15779
+ break;
15291
15780
  }
15292
- const targetMinX = Math.max(...nearby2.map((bounds) => bounds.maxX));
15293
- return Math.max(0, targetMinX - labelBounds.minX + LABEL_CLEARANCE2);
15294
15781
  }
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
- }
15303
- const targetMinY = Math.max(...nearby.map((bounds) => bounds.maxY));
15304
- return Math.max(0, targetMinY - labelBounds.minY + LABEL_CLEARANCE2);
15782
+ return distance7;
15305
15783
  };
15306
15784
  var moveLabel = (label, orientation, distance7) => {
15307
15785
  const direction = dir(orientation);
@@ -15336,10 +15814,9 @@ var boundsGapOnPerpendicularAxis = (a, b, orientation) => {
15336
15814
  return Math.max(0, a.minX - b.maxX, b.minX - a.maxX);
15337
15815
  };
15338
15816
  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) => {
15817
+ var findConnectorTraceIndex = (label, traces, connectorTraceIds) => traces.findIndex((trace) => {
15341
15818
  if (trace.globalConnNetId !== label.globalConnNetId) return false;
15342
- if (!isGeneratedLabelConnector(trace)) return false;
15819
+ if (!connectorTraceIds.has(trace.mspPairId)) return false;
15343
15820
  const first = trace.tracePath[0];
15344
15821
  const last = trace.tracePath.at(-1);
15345
15822
  return Boolean(
@@ -15422,8 +15899,10 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15422
15899
  inputProblem,
15423
15900
  traces,
15424
15901
  netLabelPlacements,
15425
- inlineNetLabelPlacements
15902
+ inlineNetLabelPlacements,
15903
+ netLabelConnectorTraceIds = /* @__PURE__ */ new Set()
15426
15904
  }) => {
15905
+ const connectorTraceIds = new Set(netLabelConnectorTraceIds);
15427
15906
  const outputTraces = traces.map((trace) => ({
15428
15907
  ...trace,
15429
15908
  tracePath: trace.tracePath.map((point) => ({ ...point }))
@@ -15467,7 +15946,11 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15467
15946
  moveLabel(obstacle, trigger.orientation, existingObstacleDistance)
15468
15947
  );
15469
15948
  if (!boundsOverlap(movingBounds, obstacleBounds)) continue;
15470
- if (!sharesOwnerChip(obstacle, ownerChipIds, chipIdByPinId) || findConnectorTraceIndex(obstacle, outputTraces) === -1 && !canAddConnectorAtAnchor(obstacle, outputTraces, pinMap)) {
15949
+ if (!sharesOwnerChip(obstacle, ownerChipIds, chipIdByPinId) || findConnectorTraceIndex(
15950
+ obstacle,
15951
+ outputTraces,
15952
+ connectorTraceIds
15953
+ ) === -1 && !canAddConnectorAtAnchor(obstacle, outputTraces, pinMap)) {
15471
15954
  failed = true;
15472
15955
  break;
15473
15956
  }
@@ -15539,7 +16022,11 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15539
16022
  const connectorUpdates = [];
15540
16023
  for (const [labelIndex, movedLabel] of proposals) {
15541
16024
  const label = outputLabels[labelIndex];
15542
- const connectorIndex = findConnectorTraceIndex(label, outputTraces);
16025
+ const connectorIndex = findConnectorTraceIndex(
16026
+ label,
16027
+ outputTraces,
16028
+ connectorTraceIds
16029
+ );
15543
16030
  if (connectorIndex === -1 && !canAddConnectorAtAnchor(label, outputTraces, pinMap)) {
15544
16031
  failed = true;
15545
16032
  break;
@@ -15589,6 +16076,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15589
16076
  movedLabelIndices.add(labelIndex);
15590
16077
  }
15591
16078
  for (const update of connectorUpdates) {
16079
+ connectorTraceIds.add(update.trace.mspPairId);
15592
16080
  if (update.connectorIndex === -1) outputTraces.push(update.trace);
15593
16081
  else outputTraces[update.connectorIndex] = update.trace;
15594
16082
  }
@@ -15596,7 +16084,8 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15596
16084
  return {
15597
16085
  traces: outputTraces,
15598
16086
  netLabelPlacements: outputLabels,
15599
- movedLabelCount: movedLabelIndices.size
16087
+ movedLabelCount: movedLabelIndices.size,
16088
+ netLabelConnectorTraceIds: connectorTraceIds
15600
16089
  };
15601
16090
  };
15602
16091
 
@@ -15798,7 +16287,7 @@ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
15798
16287
  };
15799
16288
 
15800
16289
  // lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts
15801
- import { getSegmentIntersection as getSegmentIntersection5 } from "@tscircuit/math-utils/line-intersections";
16290
+ import { getSegmentIntersection as getSegmentIntersection6 } from "@tscircuit/math-utils/line-intersections";
15802
16291
 
15803
16292
  // lib/utils/pathIntersectsRenderedLabel.ts
15804
16293
  import {
@@ -15826,31 +16315,31 @@ var pathIntersectsRenderedLabel = (path, label) => {
15826
16315
  };
15827
16316
 
15828
16317
  // lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts
15829
- var EPS15 = 1e-6;
16318
+ var EPS16 = 1e-6;
15830
16319
  var getPathLength7 = (path) => path.slice(1).reduce((length, point, pointIndex) => {
15831
16320
  const previousPoint = path[pointIndex];
15832
16321
  return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
15833
16322
  }, 0);
15834
16323
  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
16324
+ (point, index) => Math.abs(point.x - second[index].x) <= EPS16 && Math.abs(point.y - second[index].y) <= EPS16
15836
16325
  );
15837
- var isStrictlySimpler = (candidate, current) => candidate.length < current.length && getPathLength7(candidate) <= getPathLength7(current) + EPS15;
16326
+ var isStrictlySimpler = (candidate, current) => candidate.length < current.length && getPathLength7(candidate) <= getPathLength7(current) + EPS16;
15838
16327
  var getIntersectionKeys = (path, otherPath) => {
15839
- const intersections = /* @__PURE__ */ new Set();
16328
+ const intersections2 = /* @__PURE__ */ new Set();
15840
16329
  for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
15841
16330
  for (let otherPathIndex = 0; otherPathIndex < otherPath.length - 1; otherPathIndex++) {
15842
- const point = getSegmentIntersection5(
16331
+ const point = getSegmentIntersection6(
15843
16332
  path[pathIndex],
15844
16333
  path[pathIndex + 1],
15845
16334
  otherPath[otherPathIndex],
15846
16335
  otherPath[otherPathIndex + 1]
15847
16336
  );
15848
16337
  if (point) {
15849
- intersections.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
16338
+ intersections2.add(`${point.x.toFixed(6)},${point.y.toFixed(6)}`);
15850
16339
  }
15851
16340
  }
15852
16341
  }
15853
- return intersections;
16342
+ return intersections2;
15854
16343
  };
15855
16344
  var introducesNewCrossings = (candidatePath, currentPath, otherPaths) => otherPaths.some((otherPath) => {
15856
16345
  const currentIntersections = getIntersectionKeys(currentPath, otherPath);
@@ -15927,9 +16416,7 @@ var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
15927
16416
  var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
15928
16417
  var INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
15929
16418
  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("::");
16419
+ var getPinPairKey2 = (pinIds) => JSON.stringify([...pinIds].sort());
15933
16420
  var getTraceLength2 = (trace) => {
15934
16421
  let length = 0;
15935
16422
  for (let index = 0; index < trace.tracePath.length - 1; index++) {
@@ -15950,21 +16437,26 @@ var getInlinePlacementBounds = (placement) => {
15950
16437
  maxY: placement.center.y + renderedHeight / 2
15951
16438
  };
15952
16439
  };
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;
16440
+ var getInlinePlacementKey = (placement) => JSON.stringify([
16441
+ placement.globalConnNetId,
16442
+ [...placement.pinIds].sort(),
16443
+ placement.mspPairId ?? null
16444
+ ]);
16445
+ var terminalStubIntersectsTrace = (path, trace) => {
16446
+ const clearance = INLINE_NET_LABEL_STUB_TRACE_CLEARANCE - GEOMETRY_EPSILON;
16447
+ const bounds = {
16448
+ minX: Math.min(path[0].x, path[1].x) - clearance,
16449
+ maxX: Math.max(path[0].x, path[1].x) + clearance,
16450
+ minY: Math.min(path[0].y, path[1].y) - clearance,
16451
+ maxY: Math.max(path[0].y, path[1].y) + clearance
16452
+ };
16453
+ return doesPathIntersectBounds3(trace.tracePath, bounds);
16454
+ };
16455
+ var InlineNetLabelSolver = class _InlineNetLabelSolver extends BaseSolver {
15966
16456
  constructor(input) {
15967
16457
  super();
16458
+ this.input = input;
16459
+ this.netLabelConnectorTraceIds = input.netLabelConnectorTraceIds ?? /* @__PURE__ */ new Set();
15968
16460
  this.inputProblem = input.inputProblem;
15969
16461
  this.traces = input.traces;
15970
16462
  this.inputNetLabelPlacements = input.netLabelPlacements;
@@ -15974,7 +16466,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
15974
16466
  ).netConnMap;
15975
16467
  this.queuedConnections = [
15976
16468
  ...this.inputProblem.directConnections.filter(
15977
- (connection) => connection.allowInlineNetLabel && connection.netId
16469
+ (connection) => connection.allowInlineNetLabel && connection.netId && // An opted-in named net owns all of its connected components.
16470
+ // Do not plan the same pair again through its direct connection.
16471
+ !this.inputProblem.netConnections.some(
16472
+ (net) => net.allowInlineNetLabel && net.netId && connection.pinIds.every((pin) => net.pinIds.includes(pin))
16473
+ )
15978
16474
  ).map((connection) => ({
15979
16475
  kind: "direct_connection",
15980
16476
  connection
@@ -15988,7 +16484,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
15988
16484
  ];
15989
16485
  this.tracesByPinPairKey = /* @__PURE__ */ new Map();
15990
16486
  for (const trace of this.traces) {
15991
- if (isGeneratedAnchoredLabelConnector(trace)) continue;
16487
+ if (this.netLabelConnectorTraceIds.has(trace.mspPairId)) continue;
15992
16488
  const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
15993
16489
  const existing = this.tracesByPinPairKey.get(key);
15994
16490
  if (existing) {
@@ -15998,13 +16494,32 @@ var InlineNetLabelSolver = class extends BaseSolver {
15998
16494
  }
15999
16495
  }
16000
16496
  }
16497
+ input;
16498
+ inputProblem;
16499
+ traces;
16500
+ inputNetLabelPlacements;
16501
+ completedReroutes;
16502
+ inlineNetLabelPlacements = [];
16503
+ /** Eligible connections still waiting to be processed. */
16504
+ queuedConnections;
16505
+ netLabelConnectorTraceIds;
16506
+ tracesByPinPairKey;
16507
+ globalConnMap;
16508
+ inlineConversions = [];
16509
+ hasAlignedPortOnlyStubs = false;
16510
+ postProcessedOutput;
16511
+ refinementComplete = false;
16512
+ refinement;
16513
+ currentProposal;
16001
16514
  getConstructorParams() {
16002
16515
  return [
16003
16516
  {
16004
16517
  inputProblem: this.inputProblem,
16005
16518
  traces: this.traces,
16006
16519
  netLabelPlacements: this.inputNetLabelPlacements,
16007
- completedReroutes: this.completedReroutes
16520
+ completedReroutes: this.completedReroutes,
16521
+ resolveTraceCollisions: this.input.resolveTraceCollisions,
16522
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
16008
16523
  }
16009
16524
  ];
16010
16525
  }
@@ -16025,16 +16540,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
16025
16540
  netConnections
16026
16541
  )) {
16027
16542
  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
- );
16543
+ this.inlineConversions.push(conversion);
16038
16544
  }
16039
16545
  return;
16040
16546
  }
@@ -16048,11 +16554,12 @@ var InlineNetLabelSolver = class extends BaseSolver {
16048
16554
  });
16049
16555
  if (placement) this.inlineNetLabelPlacements.push(placement);
16050
16556
  } 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);
16557
+ for (const pinId of connection.pinIds) {
16558
+ const placement = this.computeTerminalInlinePlacement(
16559
+ connection,
16560
+ pinId
16561
+ );
16562
+ if (placement) this.inlineNetLabelPlacements.push(placement);
16056
16563
  }
16057
16564
  }
16058
16565
  return;
@@ -16071,13 +16578,17 @@ var InlineNetLabelSolver = class extends BaseSolver {
16071
16578
  this.postProcessedOutput = this.buildPostProcessedOutput();
16072
16579
  return;
16073
16580
  }
16581
+ if (this.input.resolveTraceCollisions && !this.refinementComplete) {
16582
+ this.stepTraceRefinement();
16583
+ return;
16584
+ }
16074
16585
  this.solved = true;
16075
16586
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
16076
16587
  }
16077
16588
  /**
16078
16589
  * Plans all opted-in multi-pin nets together. Short routed components can
16079
16590
  * mutually block the outward endpoint stubs that replace them, so tentative
16080
- * replacements are ignored as obstacles only while their owning net still
16591
+ * replacements are ignored as obstacles only while their connected component still
16081
16592
  * converts successfully. Failed conversions retain both their traces and
16082
16593
  * their conventional labels, and the remaining conversions are retried.
16083
16594
  */
@@ -16118,13 +16629,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
16118
16629
  }));
16119
16630
  const nextIgnoredTraceIds = new Set(
16120
16631
  conversions.flatMap(
16121
- ({ conversion }) => conversion ? [...conversion.supersededTraceIds] : []
16632
+ ({ conversion }) => conversion.flatMap((component) => [...component.supersededTraceIds])
16122
16633
  )
16123
16634
  );
16124
16635
  if (setsEqual(ignoredTraceIds, nextIgnoredTraceIds)) {
16125
- return conversions.flatMap(
16126
- ({ conversion }) => conversion ? [conversion] : []
16127
- );
16636
+ return conversions.flatMap(({ conversion }) => conversion);
16128
16637
  }
16129
16638
  ignoredTraceIds = nextIgnoredTraceIds;
16130
16639
  }
@@ -16154,7 +16663,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
16154
16663
  // anchored label. They are removed when that label becomes inline, so
16155
16664
  // treating them as routed circuitry would leave text on a deleted wire.
16156
16665
  traces: this.traces.filter(
16157
- (trace) => !isGeneratedAnchoredLabelConnector(trace)
16666
+ (trace) => !this.netLabelConnectorTraceIds.has(trace.mspPairId)
16158
16667
  )
16159
16668
  }).map((component) => ({
16160
16669
  pinIds: component.pinIds,
@@ -16180,14 +16689,14 @@ var InlineNetLabelSolver = class extends BaseSolver {
16180
16689
  * Converts every conventional label representation for an opted-in named
16181
16690
  * net. Routed connected components become labels on their representative
16182
16691
  * trace; disconnected pins become outward terminal stubs. The conversion is
16183
- * atomic so one blocked candidate cannot leave mixed label styles on a net.
16692
+ * atomic within each routed component, so replacing its traces cannot strand
16693
+ * a pin. Disconnected components can keep their own label style.
16184
16694
  */
16185
16695
  computeNetConnectionInlinePlacements(connection, ignoredTraceIds, forcedTerminalTraceIds) {
16186
16696
  const components = this.getNetConnectionComponents(connection);
16187
16697
  const globalConnNetId = this.getGlobalConnNetId(connection);
16188
- if (!globalConnNetId) return null;
16189
- const inlinePlacements = [];
16190
- const supersededTraceIds = /* @__PURE__ */ new Set();
16698
+ if (!globalConnNetId) return [];
16699
+ const conversions = [];
16191
16700
  for (const component of components) {
16192
16701
  const shouldUseTerminalStubs = component.traces.some(
16193
16702
  (trace) => forcedTerminalTraceIds.has(trace.mspPairId)
@@ -16205,14 +16714,18 @@ var InlineNetLabelSolver = class extends BaseSolver {
16205
16714
  }
16206
16715
  }
16207
16716
  if (inlinePlacement) {
16208
- inlinePlacements.push(inlinePlacement);
16717
+ conversions.push({
16718
+ globalConnNetId,
16719
+ placements: [inlinePlacement],
16720
+ supersededTraceIds: /* @__PURE__ */ new Set()
16721
+ });
16209
16722
  continue;
16210
16723
  }
16211
16724
  if (component.traces.length > 0 && !shouldUseTerminalStubs) {
16212
- return null;
16725
+ continue;
16213
16726
  }
16214
16727
  if (component.labeledPinIds.length !== component.pinIds.length) {
16215
- return null;
16728
+ continue;
16216
16729
  }
16217
16730
  const terminalPlacements = component.pinIds.map(
16218
16731
  (pinId) => this.computeTerminalInlinePlacement(
@@ -16223,26 +16736,41 @@ var InlineNetLabelSolver = class extends BaseSolver {
16223
16736
  )
16224
16737
  );
16225
16738
  if (terminalPlacements.some((placement) => placement === null)) {
16226
- return null;
16739
+ continue;
16227
16740
  }
16228
- inlinePlacements.push(
16229
- ...terminalPlacements.filter(
16230
- (placement) => placement !== null
16741
+ conversions.push({
16742
+ globalConnNetId,
16743
+ placements: terminalPlacements,
16744
+ supersededTraceIds: new Set(
16745
+ component.traces.map((trace) => trace.mspPairId)
16231
16746
  )
16232
- );
16233
- for (const trace of component.traces) {
16234
- supersededTraceIds.add(trace.mspPairId);
16747
+ });
16748
+ }
16749
+ return conversions;
16750
+ }
16751
+ /** Prefer the normal gap, then center text in a tighter parallel-wire slot. */
16752
+ getInlineOffsets(anchor, axis, side, width, height, traces, ignoredTraceIds) {
16753
+ const normal = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
16754
+ const across = axis === "x" ? "y" : "x";
16755
+ const sign = side === "x+" || side === "y+" ? 1 : -1;
16756
+ let gap = Infinity;
16757
+ for (const trace of traces) {
16758
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
16759
+ for (const segment of getAxisAlignedSegments2(trace.tracePath)) {
16760
+ if (segment.axis !== axis) continue;
16761
+ 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)
16762
+ continue;
16763
+ const distance7 = sign * (segment.start[across] - anchor[across]);
16764
+ if (distance7 > GEOMETRY_EPSILON) gap = Math.min(gap, distance7);
16235
16765
  }
16236
16766
  }
16237
- return { globalConnNetId, placements: inlinePlacements, supersededTraceIds };
16767
+ return gap > height + GEOMETRY_EPSILON && gap / 2 < normal - GEOMETRY_EPSILON ? [normal, gap / 2] : [normal];
16238
16768
  }
16239
16769
  /**
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.
16770
+ * Converts one conventional endpoint placement into an inline label on an
16771
+ * outward stub that follows the pin's true facing direction.
16244
16772
  */
16245
- computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set()) {
16773
+ computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set(), obstacles) {
16246
16774
  if (!connection.netId) return null;
16247
16775
  const anchoredPlacement = this.inputNetLabelPlacements.find(
16248
16776
  (placement) => placement.netId === connection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
@@ -16265,7 +16793,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
16265
16793
  start,
16266
16794
  intendedEnd,
16267
16795
  minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
16268
- traces: this.traces,
16796
+ traces: obstacles?.traces ?? this.traces,
16269
16797
  ownGlobalConnNetId: globalConnNetId,
16270
16798
  ignoredTraceIds
16271
16799
  });
@@ -16275,42 +16803,52 @@ var InlineNetLabelSolver = class extends BaseSolver {
16275
16803
  x: (start.x + end.x) / 2,
16276
16804
  y: (start.y + end.y) / 2
16277
16805
  };
16278
- const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
16279
16806
  const sides = axis === "x" ? ["y+", "y-"] : ["x-", "x+"];
16280
16807
  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,
16808
+ for (const offset of this.getInlineOffsets(
16308
16809
  anchorPoint,
16309
- center,
16810
+ axis,
16811
+ side,
16310
16812
  width,
16311
16813
  height,
16312
- side
16313
- };
16814
+ obstacles?.traces ?? this.traces,
16815
+ ignoredTraceIds
16816
+ )) {
16817
+ 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 };
16818
+ const halfAlong = width / 2;
16819
+ const halfAcross = height / 2;
16820
+ const bounds = axis === "x" ? {
16821
+ minX: center.x - halfAlong,
16822
+ maxX: center.x + halfAlong,
16823
+ minY: center.y - halfAcross,
16824
+ maxY: center.y + halfAcross
16825
+ } : {
16826
+ minX: center.x - halfAcross,
16827
+ maxX: center.x + halfAcross,
16828
+ minY: center.y - halfAlong,
16829
+ maxY: center.y + halfAlong
16830
+ };
16831
+ if (this.isObstructed(bounds, {
16832
+ ownGlobalConnNetId: globalConnNetId,
16833
+ ignoredTraceIds,
16834
+ obstacles
16835
+ })) {
16836
+ continue;
16837
+ }
16838
+ return {
16839
+ globalConnNetId,
16840
+ netId: connection.netId,
16841
+ netLabelText,
16842
+ pinIds: [pinId],
16843
+ stubTracePath: [start, end],
16844
+ axis,
16845
+ anchorPoint,
16846
+ center,
16847
+ width,
16848
+ height,
16849
+ side
16850
+ };
16851
+ }
16314
16852
  }
16315
16853
  return null;
16316
16854
  }
@@ -16331,39 +16869,49 @@ var InlineNetLabelSolver = class extends BaseSolver {
16331
16869
  const sides = segment.axis === "x" ? ["y+", "y-"] : ["x-", "x+"];
16332
16870
  for (const side of sides) {
16333
16871
  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,
16872
+ for (const offset2 of this.getInlineOffsets(
16361
16873
  anchorPoint,
16362
- center,
16874
+ segment.axis,
16875
+ side,
16363
16876
  width,
16364
16877
  height,
16365
- side
16366
- };
16878
+ this.traces,
16879
+ ignoredTraceIds
16880
+ )) {
16881
+ 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 };
16882
+ const halfAlong = width / 2;
16883
+ const halfAcross = height / 2;
16884
+ const bounds = segment.axis === "x" ? {
16885
+ minX: center.x - halfAlong,
16886
+ maxX: center.x + halfAlong,
16887
+ minY: center.y - halfAcross,
16888
+ maxY: center.y + halfAcross
16889
+ } : {
16890
+ minX: center.x - halfAcross,
16891
+ maxX: center.x + halfAcross,
16892
+ minY: center.y - halfAlong,
16893
+ maxY: center.y + halfAlong
16894
+ };
16895
+ if (this.isObstructed(bounds, {
16896
+ ownTrace: trace,
16897
+ ownGlobalConnNetId: trace.globalConnNetId,
16898
+ ignoredTraceIds
16899
+ }))
16900
+ continue;
16901
+ return {
16902
+ globalConnNetId: trace.globalConnNetId,
16903
+ netId: connection.netId,
16904
+ netLabelText,
16905
+ mspPairId: trace.mspPairId,
16906
+ pinIds: [...pinIds],
16907
+ axis: segment.axis,
16908
+ anchorPoint,
16909
+ center,
16910
+ width,
16911
+ height,
16912
+ side
16913
+ };
16914
+ }
16367
16915
  }
16368
16916
  }
16369
16917
  }
@@ -16473,7 +17021,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
16473
17021
  isObstructed(bounds, {
16474
17022
  ownTrace,
16475
17023
  ownGlobalConnNetId,
16476
- ignoredTraceIds = /* @__PURE__ */ new Set()
17024
+ ignoredTraceIds = /* @__PURE__ */ new Set(),
17025
+ obstacles
16477
17026
  }) {
16478
17027
  for (const chip of this.inputProblem.chips) {
16479
17028
  const chipBounds = {
@@ -16487,82 +17036,102 @@ var InlineNetLabelSolver = class extends BaseSolver {
16487
17036
  for (const textBox of this.inputProblem.textBoxes ?? []) {
16488
17037
  if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
16489
17038
  }
16490
- for (const trace of this.traces) {
17039
+ for (const trace of obstacles?.traces ?? this.traces) {
16491
17040
  if (ignoredTraceIds.has(trace.mspPairId)) continue;
16492
17041
  if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
16493
17042
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
16494
17043
  if (doesPathIntersectBounds3(trace.tracePath, bounds)) return true;
16495
17044
  }
17045
+ if (obstacles?.anchored.some(
17046
+ (label) => boundsOverlap(bounds, getAnchoredNetLabelRenderedBounds(label))
17047
+ ))
17048
+ return true;
17049
+ if (obstacles?.inline.some(
17050
+ (label) => boundsOverlap(bounds, getInlinePlacementBounds(label)) || label.stubTracePath && label.globalConnNetId !== ownGlobalConnNetId && doesPathIntersectBounds3(label.stubTracePath, bounds)
17051
+ ))
17052
+ return true;
16496
17053
  return false;
16497
17054
  }
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) {
17055
+ getOutputTraces(placements) {
17056
+ const activeKeys = new Set(placements.map(getInlinePlacementKey));
17057
+ const supersededTraceIds = new Set(
17058
+ this.inlineConversions.filter(
17059
+ (conversion) => conversion.placements.every(
17060
+ (placement) => activeKeys.has(getInlinePlacementKey(placement))
17061
+ )
17062
+ ).flatMap((conversion) => [...conversion.supersededTraceIds])
17063
+ );
16510
17064
  return this.traces.filter(
16511
- (trace) => !supersededTraceIds.has(trace.mspPairId) && !(supersededNetLabelKeys.has(trace.globalConnNetId) && isGeneratedAnchoredLabelConnector(trace))
17065
+ (trace) => !supersededTraceIds.has(trace.mspPairId) && !(this.netLabelConnectorTraceIds.has(trace.mspPairId) && isLabelRepresentedInline(trace, placements))
16512
17066
  );
16513
17067
  }
16514
- getInlineGlobalsOverlappingAnchoredLabels({
17068
+ getBlockedInlinePlacements({
16515
17069
  inlineNetLabelPlacements,
16516
- anchoredNetLabelPlacements
17070
+ anchoredNetLabelPlacements,
17071
+ traces
16517
17072
  }) {
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
- }
17073
+ const blockedKeys = /* @__PURE__ */ new Set();
17074
+ for (const inline of inlineNetLabelPlacements) {
17075
+ const bounds = getInlinePlacementBounds(inline);
17076
+ const blockedByLabel = anchoredNetLabelPlacements.some((anchored) => {
17077
+ const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchored);
17078
+ return boundsOverlap(bounds, anchoredBounds) || inline.stubTracePath && doesPathIntersectBounds3(inline.stubTracePath, anchoredBounds);
17079
+ });
17080
+ const blockedByTrace = traces.some(
17081
+ (trace) => trace.globalConnNetId !== inline.globalConnNetId && (doesPathIntersectBounds3(trace.tracePath, bounds) || inline.stubTracePath && terminalStubIntersectsTrace(inline.stubTracePath, trace))
17082
+ );
17083
+ const blockedByInlineWire = inlineNetLabelPlacements.some(
17084
+ (other) => other !== inline && other.globalConnNetId !== inline.globalConnNetId && other.stubTracePath && doesPathIntersectBounds3(other.stubTracePath, bounds)
17085
+ );
17086
+ const blockedByInlineText = inlineNetLabelPlacements.some(
17087
+ (other) => other !== inline && boundsOverlap(bounds, getInlinePlacementBounds(other))
17088
+ );
17089
+ if (blockedByLabel || blockedByTrace || blockedByInlineWire || blockedByInlineText)
17090
+ blockedKeys.add(getInlinePlacementKey(inline));
17091
+ }
17092
+ for (const conversion of this.inlineConversions) {
17093
+ if (conversion.placements.some(
17094
+ (placement) => blockedKeys.has(getInlinePlacementKey(placement))
17095
+ )) {
17096
+ for (const placement of conversion.placements)
17097
+ blockedKeys.add(getInlinePlacementKey(placement));
16533
17098
  }
16534
17099
  }
16535
- return blockedGlobalConnNetIds;
17100
+ return blockedKeys;
16536
17101
  }
16537
17102
  buildPostProcessedOutput() {
16538
17103
  let activeInlinePlacements = this.inlineNetLabelPlacements;
16539
17104
  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
17105
  const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
16549
- (placement) => !supersededNetLabelKeys.has(placement.globalConnNetId)
17106
+ (placement) => !isLabelRepresentedInline(placement, activeInlinePlacements)
16550
17107
  );
16551
- const outputTraces = this.getOutputTraces(
16552
- supersededNetLabelKeys,
16553
- supersededTraceIds
17108
+ const outputTraces = this.getOutputTraces(activeInlinePlacements);
17109
+ const { fixedLabels, terminalTraces } = getInlineLabelObstacles(
17110
+ this.inputProblem,
17111
+ activeInlinePlacements
16554
17112
  );
17113
+ const labelCollisionSolver = new NetLabelNetLabelCollisionSolver({
17114
+ inputProblem: this.inputProblem,
17115
+ traces: [...outputTraces, ...terminalTraces],
17116
+ netLabelPlacements: retainedNetLabelPlacements,
17117
+ fixedNetLabelPlacements: fixedLabels,
17118
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
17119
+ useRenderedLabelBounds: activeInlinePlacements.length > 0
17120
+ });
17121
+ if (activeInlinePlacements.length > 0) labelCollisionSolver.solve();
16555
17122
  const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
16556
17123
  inputProblem: this.inputProblem,
16557
17124
  traces: outputTraces,
16558
- netLabelPlacements: retainedNetLabelPlacements,
16559
- inlineNetLabelPlacements: activeInlinePlacements
17125
+ netLabelPlacements: labelCollisionSolver.solved && !labelCollisionSolver.failed ? labelCollisionSolver.getOutput().netLabelPlacements : retainedNetLabelPlacements,
17126
+ inlineNetLabelPlacements: activeInlinePlacements,
17127
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
16560
17128
  });
16561
- const blockedGlobalConnNetIds = this.getInlineGlobalsOverlappingAnchoredLabels({
17129
+ let blockedPlacementKeys = this.getBlockedInlinePlacements({
16562
17130
  inlineNetLabelPlacements: activeInlinePlacements,
16563
- anchoredNetLabelPlacements: pushed.netLabelPlacements
17131
+ anchoredNetLabelPlacements: pushed.netLabelPlacements,
17132
+ traces: pushed.traces
16564
17133
  });
16565
- if (blockedGlobalConnNetIds.size > 0) {
17134
+ if (blockedPlacementKeys.size > 0) {
16566
17135
  const shiftedInlineTerminalLabels = pushInlineTerminalLabelsAwayFromAnchoredLabels({
16567
17136
  inputProblem: this.inputProblem,
16568
17137
  traces: pushed.traces,
@@ -16573,8 +17142,49 @@ var InlineNetLabelSolver = class extends BaseSolver {
16573
17142
  activeInlinePlacements = shiftedInlineTerminalLabels.inlineNetLabelPlacements;
16574
17143
  continue;
16575
17144
  }
17145
+ const { terminalTraces: terminalTraces2 } = getInlineLabelObstacles(
17146
+ this.inputProblem,
17147
+ activeInlinePlacements
17148
+ );
17149
+ const replanned = activeInlinePlacements.filter(
17150
+ (label) => !label.stubTracePath
17151
+ );
17152
+ const terminals = activeInlinePlacements.filter((label) => label.stubTracePath).sort(
17153
+ (a, b) => Number(blockedPlacementKeys.has(getInlinePlacementKey(b))) - Number(blockedPlacementKeys.has(getInlinePlacementKey(a)))
17154
+ );
17155
+ for (const placement of terminals) {
17156
+ const connection = [
17157
+ ...this.inputProblem.netConnections,
17158
+ ...this.inputProblem.directConnections
17159
+ ].find(
17160
+ (connection2) => connection2.netId === placement.netId && connection2.allowInlineNetLabel
17161
+ );
17162
+ const candidate = connection ? this.computeTerminalInlinePlacement(
17163
+ connection,
17164
+ placement.pinIds[0],
17165
+ placement.globalConnNetId,
17166
+ /* @__PURE__ */ new Set(),
17167
+ {
17168
+ traces: [...pushed.traces, ...terminalTraces2],
17169
+ anchored: pushed.netLabelPlacements,
17170
+ inline: replanned
17171
+ }
17172
+ ) : null;
17173
+ replanned.push(candidate ?? placement);
17174
+ }
17175
+ const byKey = new Map(
17176
+ replanned.map((label) => [getInlinePlacementKey(label), label])
17177
+ );
17178
+ activeInlinePlacements = activeInlinePlacements.map(
17179
+ (label) => byKey.get(getInlinePlacementKey(label)) ?? label
17180
+ );
17181
+ blockedPlacementKeys = this.getBlockedInlinePlacements({
17182
+ inlineNetLabelPlacements: activeInlinePlacements,
17183
+ anchoredNetLabelPlacements: pushed.netLabelPlacements,
17184
+ traces: pushed.traces
17185
+ });
16576
17186
  }
16577
- if (blockedGlobalConnNetIds.size === 0) {
17187
+ if (blockedPlacementKeys.size === 0) {
16578
17188
  this.inlineNetLabelPlacements = activeInlinePlacements;
16579
17189
  this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
16580
17190
  const restored = restoreReroutesAroundSupersededLabels({
@@ -16585,17 +17195,141 @@ var InlineNetLabelSolver = class extends BaseSolver {
16585
17195
  completedReroutes: this.completedReroutes
16586
17196
  });
16587
17197
  this.stats.restoredSupersededLabelRerouteCount = restored.restoredTraceCount;
16588
- return {
17198
+ const output = {
16589
17199
  traces: restored.traces,
17200
+ netLabelConnectorTraceIds: pushed.netLabelConnectorTraceIds,
16590
17201
  netLabelPlacements: pushed.netLabelPlacements,
16591
17202
  inlineNetLabelPlacements: activeInlinePlacements
16592
17203
  };
17204
+ this.inlineNetLabelPlacements = output.inlineNetLabelPlacements;
17205
+ return output;
16593
17206
  }
16594
17207
  activeInlinePlacements = activeInlinePlacements.filter(
16595
- (placement) => !blockedGlobalConnNetIds.has(placement.globalConnNetId)
17208
+ (placement) => !blockedPlacementKeys.has(getInlinePlacementKey(placement))
17209
+ );
17210
+ }
17211
+ }
17212
+ /** Enumerate nearby moves in the existing order; collect one proposal per step. */
17213
+ *getTraceRefinementProposals(current) {
17214
+ yield* getLocalTraceLabelShifts(this.inputProblem, current);
17215
+ for (const label of current.netLabelPlacements) {
17216
+ if (label.pinIds.length !== 1) continue;
17217
+ const connection = [
17218
+ ...this.inputProblem.netConnections,
17219
+ ...this.inputProblem.directConnections
17220
+ ].find(
17221
+ (connection2) => connection2.allowInlineNetLabel && connection2.netId === label.netId && connection2.pinIds.includes(label.pinIds[0])
17222
+ );
17223
+ if (!connection) continue;
17224
+ const placement = this.computeTerminalInlinePlacement(
17225
+ connection,
17226
+ label.pinIds[0],
17227
+ label.globalConnNetId,
17228
+ /* @__PURE__ */ new Set(),
17229
+ { traces: [], anchored: [], inline: [] }
16596
17230
  );
17231
+ if (placement)
17232
+ yield* getLocalTraceLabelShifts(this.inputProblem, current, [placement]);
16597
17233
  }
16598
17234
  }
17235
+ createRefinementCandidate(proposal) {
17236
+ const traceMap = new Map(
17237
+ proposal.traces.map((trace) => [trace.mspPairId, trace])
17238
+ );
17239
+ const labelKey = (label) => JSON.stringify([label.globalConnNetId, [...label.pinIds].sort()]);
17240
+ const labelMap = new Map(
17241
+ proposal.netLabelPlacements.map((label) => [labelKey(label), label])
17242
+ );
17243
+ const originalTraceIds = new Set(
17244
+ this.traces.map((trace) => trace.mspPairId)
17245
+ );
17246
+ return new _InlineNetLabelSolver({
17247
+ inputProblem: this.inputProblem,
17248
+ traces: [
17249
+ ...this.traces.map((trace) => traceMap.get(trace.mspPairId) ?? trace),
17250
+ ...proposal.traces.filter(
17251
+ (trace) => !originalTraceIds.has(trace.mspPairId)
17252
+ )
17253
+ ],
17254
+ netLabelPlacements: this.inputNetLabelPlacements.map(
17255
+ (label) => labelMap.get(labelKey(label)) ?? label
17256
+ ),
17257
+ completedReroutes: this.completedReroutes,
17258
+ netLabelConnectorTraceIds: proposal.netLabelConnectorTraceIds
17259
+ });
17260
+ }
17261
+ acceptRefinementCandidate(proposal, result) {
17262
+ const current = this.postProcessedOutput;
17263
+ const before = this.refinement.before;
17264
+ const after = getOutputLabelCollisions(result);
17265
+ if (after.some(
17266
+ (collision) => !before.some(
17267
+ (previous) => sameOutputLabelCollision(collision, previous)
17268
+ )
17269
+ ))
17270
+ return false;
17271
+ const movedTraceIds = new Set(
17272
+ proposal.traces.filter((trace, index) => trace !== current.traces[index]).map((trace) => trace.mspPairId)
17273
+ );
17274
+ if (after.some(
17275
+ (collision) => collision.kind === "trace-label" && collision.trace.kind === "routed" && movedTraceIds.has(collision.trace.id)
17276
+ ))
17277
+ return false;
17278
+ if (after.length >= before.length && result.inlineNetLabelPlacements.length <= current.inlineNetLabelPlacements.length)
17279
+ return false;
17280
+ this.postProcessedOutput = result;
17281
+ this.inlineNetLabelPlacements = result.inlineNetLabelPlacements;
17282
+ return true;
17283
+ }
17284
+ stepTraceRefinement() {
17285
+ if (this.activeSubSolver) {
17286
+ this.activeSubSolver.step();
17287
+ if (!this.activeSubSolver.solved && !this.activeSubSolver.failed) return;
17288
+ if (this.activeSubSolver.solved && !this.activeSubSolver.failed && this.acceptRefinementCandidate(
17289
+ this.currentProposal,
17290
+ this.activeSubSolver.getOutput()
17291
+ )) {
17292
+ this.refinement = void 0;
17293
+ }
17294
+ this.activeSubSolver = null;
17295
+ this.currentProposal = void 0;
17296
+ return;
17297
+ }
17298
+ const current = this.postProcessedOutput;
17299
+ if (!this.refinement) {
17300
+ this.refinement = {
17301
+ proposals: this.getTraceRefinementProposals(current),
17302
+ queue: [],
17303
+ collecting: true,
17304
+ before: getOutputLabelCollisions(current)
17305
+ };
17306
+ }
17307
+ const refinement = this.refinement;
17308
+ if (refinement.collecting) {
17309
+ const next2 = refinement.proposals.next();
17310
+ if (!next2.done) {
17311
+ const distance7 = next2.value.traces.reduce(
17312
+ (sum, trace, index) => sum + trace.tracePath.reduce((distance8, point, pointIndex) => {
17313
+ const previous = current.traces[index].tracePath[pointIndex];
17314
+ return distance8 + Math.abs(point.x - previous.x) + Math.abs(point.y - previous.y);
17315
+ }, 0),
17316
+ 0
17317
+ );
17318
+ refinement.queue.push({ output: next2.value, distance: distance7 });
17319
+ } else {
17320
+ refinement.queue.sort((a, b) => a.distance - b.distance);
17321
+ refinement.collecting = false;
17322
+ }
17323
+ return;
17324
+ }
17325
+ const next = refinement.queue.shift();
17326
+ if (!next) {
17327
+ this.refinementComplete = true;
17328
+ return;
17329
+ }
17330
+ this.currentProposal = next.output;
17331
+ this.activeSubSolver = this.createRefinementCandidate(next.output);
17332
+ }
16599
17333
  getOutput() {
16600
17334
  if (this.postProcessedOutput) return this.postProcessedOutput;
16601
17335
  return this.buildPostProcessedOutput();
@@ -16774,12 +17508,12 @@ var getCollisionLimitedTerminalStubEnd = ({
16774
17508
  const maxAcross = Math.max(startAcross, endAcross);
16775
17509
  let collisionDistance;
16776
17510
  const isCollinear = Math.abs(startAcross) <= GEOMETRY_EPSILON && Math.abs(endAcross) <= GEOMETRY_EPSILON;
16777
- if (isCollinear && maxAlong >= -GEOMETRY_EPSILON && minAlong <= intendedLength + GEOMETRY_EPSILON) {
17511
+ if (isCollinear && maxAlong >= -GEOMETRY_EPSILON && minAlong <= intendedLength + INLINE_NET_LABEL_STUB_TRACE_CLEARANCE + GEOMETRY_EPSILON) {
16778
17512
  collisionDistance = Math.max(0, minAlong);
16779
17513
  } else {
16780
17514
  const crossesStubAxis = minAcross <= GEOMETRY_EPSILON && maxAcross >= -GEOMETRY_EPSILON;
16781
17515
  const perpendicularAlong = (startAlong + endAlong) / 2;
16782
- if (crossesStubAxis && Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON && perpendicularAlong >= -GEOMETRY_EPSILON && perpendicularAlong <= intendedLength + GEOMETRY_EPSILON) {
17516
+ if (crossesStubAxis && Math.abs(startAlong - endAlong) <= GEOMETRY_EPSILON && perpendicularAlong >= -GEOMETRY_EPSILON && perpendicularAlong <= intendedLength + INLINE_NET_LABEL_STUB_TRACE_CLEARANCE + GEOMETRY_EPSILON) {
16783
17517
  collisionDistance = Math.max(0, perpendicularAlong);
16784
17518
  }
16785
17519
  }
@@ -16889,12 +17623,12 @@ var getTraceRecoveryConnectivityMaps = (inputProblem) => {
16889
17623
  };
16890
17624
 
16891
17625
  // 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;
17626
+ var EPS17 = 1e-9;
17627
+ var isHorizontal4 = (start, end) => Math.abs(start.y - end.y) < EPS17;
17628
+ var isVertical5 = (start, end) => Math.abs(start.x - end.x) < EPS17;
16895
17629
  var compactConsecutivePoints = (path) => path.filter((point, index) => {
16896
17630
  const previousPoint = path[index - 1];
16897
- return !previousPoint || Math.abs(point.x - previousPoint.x) >= EPS16 || Math.abs(point.y - previousPoint.y) >= EPS16;
17631
+ return !previousPoint || Math.abs(point.x - previousPoint.x) >= EPS17 || Math.abs(point.y - previousPoint.y) >= EPS17;
16898
17632
  });
16899
17633
  var isValidOrthogonalPath = (path) => {
16900
17634
  if (path.length < 2 || !path.every((point, index) => {
@@ -16932,7 +17666,7 @@ var getEndpointAlignedSegmentCandidates = (tracePath) => {
16932
17666
  }
16933
17667
  const alignedCoordinates = segmentIsVertical ? [previousPoint.x, nextPoint.x] : [previousPoint.y, nextPoint.y];
16934
17668
  for (const alignedCoordinate of alignedCoordinates) {
16935
- if (Math.abs(alignedCoordinate - (segmentIsVertical ? start.x : start.y)) < EPS16) {
17669
+ if (Math.abs(alignedCoordinate - (segmentIsVertical ? start.x : start.y)) < EPS17) {
16936
17670
  continue;
16937
17671
  }
16938
17672
  const candidate = tracePath.map((point) => ({ ...point }));
@@ -16990,7 +17724,6 @@ var reduceTraceCrossings = ({
16990
17724
  };
16991
17725
 
16992
17726
  // lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts
16993
- var AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-";
16994
17727
  var RECOVERED_TRACE_PREFIX = "net-label-to-trace-";
16995
17728
  var MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET = 0.05;
16996
17729
  var MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET = 0.25;
@@ -17030,6 +17763,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17030
17763
  inputProblem;
17031
17764
  outputTraces;
17032
17765
  outputNetLabelPlacements;
17766
+ recoveredTraceIds = /* @__PURE__ */ new Set();
17033
17767
  chipMap;
17034
17768
  pinMap;
17035
17769
  queuedCandidates;
@@ -17246,7 +17980,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17246
17980
  return candidates;
17247
17981
  }
17248
17982
  isSupersededConnectorTrace(trace, candidate) {
17249
- if (!trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_PREFIX)) {
17983
+ if (!this.input.netLabelConnectorTraceIds?.has(trace.mspPairId)) {
17250
17984
  return false;
17251
17985
  }
17252
17986
  if (trace.pinIds.length !== 1) return false;
@@ -17296,6 +18030,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17296
18030
  mspConnectionPairIds: [mspPairId],
17297
18031
  pinIds: [firstPin.pinId, secondPin.pinId]
17298
18032
  };
18033
+ this.recoveredTraceIds.add(mspPairId);
17299
18034
  this.outputTraces = [...retainedTraces, recoveredTrace];
17300
18035
  if (candidate.recoveryMode !== "routed_components") {
17301
18036
  this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
@@ -17355,7 +18090,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
17355
18090
  ...this.getOutput()
17356
18091
  });
17357
18092
  for (const trace of this.outputTraces) {
17358
- if (!trace.mspPairId.startsWith(RECOVERED_TRACE_PREFIX)) continue;
18093
+ if (!this.recoveredTraceIds.has(trace.mspPairId)) continue;
17359
18094
  graphics.lines.push({
17360
18095
  points: trace.tracePath,
17361
18096
  strokeColor: "green",
@@ -17821,7 +18556,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
17821
18556
  inputProblem: instance.inputProblem,
17822
18557
  traces: junctionOutput.traces,
17823
18558
  netLabelPlacements: junctionOutput.netLabelPlacements,
17824
- completedReroutes
18559
+ completedReroutes,
18560
+ resolveTraceCollisions: true,
18561
+ netLabelConnectorTraceIds: instance.availableNetOrientationSolver.netLabelConnectorTraceIds
17825
18562
  }
17826
18563
  ];
17827
18564
  }