@tscircuit/schematic-trace-solver 0.0.186 → 0.0.188

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