@tscircuit/schematic-trace-solver 0.0.36 → 0.0.38

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 (36) hide show
  1. package/dist/index.d.ts +26 -1
  2. package/dist/index.js +936 -23
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +15 -3
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +17 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +31 -12
  6. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +40 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +247 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +207 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/countTurns.ts +18 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +38 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisions.ts +26 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/index.ts +1 -0
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +73 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts +41 -0
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +229 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts +305 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts +25 -0
  18. package/package.json +1 -1
  19. package/site/examples/example20.page.tsx +103 -0
  20. package/site/examples/example21.page.tsx +177 -0
  21. package/site/examples/example22.page.tsx +108 -0
  22. package/site/examples/example23.page.tsx +137 -0
  23. package/site/examples/example24.page.tsx +128 -0
  24. package/tests/examples/__snapshots__/example16.snap.svg +27 -27
  25. package/tests/examples/__snapshots__/example21.snap.svg +272 -0
  26. package/tests/examples/__snapshots__/example22.snap.svg +137 -0
  27. package/tests/examples/__snapshots__/example23.snap.svg +137 -0
  28. package/tests/examples/__snapshots__/example24.snap.svg +143 -0
  29. package/tests/examples/__snapshots__/example25.snap.svg +165 -0
  30. package/tests/examples/__snapshots__/example26.snap.svg +157 -0
  31. package/tests/examples/example21.test.tsx +183 -0
  32. package/tests/examples/example22.test.tsx +109 -0
  33. package/tests/examples/example23.test.tsx +109 -0
  34. package/tests/examples/example24.test.tsx +114 -0
  35. package/tests/examples/example25.test.tsx +143 -0
  36. package/tests/examples/example26.test.tsx +134 -0
package/dist/index.js CHANGED
@@ -588,6 +588,16 @@ var findFirstCollision = (pts, rects, opts = {}) => {
588
588
  }
589
589
  return null;
590
590
  };
591
+ var isPathCollidingWithObstacles = (path, obstacles) => {
592
+ for (let i = 0; i < path.length - 1; i++) {
593
+ for (const obstacle of obstacles) {
594
+ if (segmentIntersectsRect(path[i], path[i + 1], obstacle)) {
595
+ return true;
596
+ }
597
+ }
598
+ }
599
+ return false;
600
+ };
591
601
 
592
602
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/mid.ts
593
603
  var EPS2 = 1e-9;
@@ -658,23 +668,33 @@ var candidateMidsFromSet = (axis, colliding, rectsById, collisionRectIds, aabb,
658
668
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts
659
669
  var EPS3 = 1e-9;
660
670
  var shiftSegmentOrth = (pts, segIndex, axis, newCoord, eps = EPS3) => {
661
- if (segIndex < 0 || segIndex >= pts.length - 1) return null;
671
+ if (segIndex < 0 || segIndex >= pts.length - 1) {
672
+ return null;
673
+ }
662
674
  const a = pts[segIndex];
663
675
  const b = pts[segIndex + 1];
664
676
  const vert = isVertical(a, b, eps);
665
677
  const horz = isHorizontal(a, b, eps);
666
- if (!vert && !horz) return null;
667
- if (vert && axis !== "x") return null;
668
- if (horz && axis !== "y") return null;
678
+ if (!vert && !horz) {
679
+ return null;
680
+ }
681
+ if (vert && axis !== "x") {
682
+ return null;
683
+ }
684
+ if (horz && axis !== "y") {
685
+ return null;
686
+ }
669
687
  const out = pts.map((p) => ({ ...p }));
670
688
  if (axis === "x") {
671
- if (Math.abs(a.x - newCoord) < eps && Math.abs(b.x - newCoord) < eps)
689
+ if (Math.abs(a.x - newCoord) < eps && Math.abs(b.x - newCoord) < eps) {
672
690
  return null;
691
+ }
673
692
  out[segIndex] = { ...out[segIndex], x: newCoord };
674
693
  out[segIndex + 1] = { ...out[segIndex + 1], x: newCoord };
675
694
  } else {
676
- if (Math.abs(a.y - newCoord) < eps && Math.abs(b.y - newCoord) < eps)
695
+ if (Math.abs(a.y - newCoord) < eps && Math.abs(b.y - newCoord) < eps) {
677
696
  return null;
697
+ }
678
698
  out[segIndex] = { ...out[segIndex], y: newCoord };
679
699
  out[segIndex + 1] = { ...out[segIndex + 1], y: newCoord };
680
700
  }
@@ -682,22 +702,31 @@ var shiftSegmentOrth = (pts, segIndex, axis, newCoord, eps = EPS3) => {
682
702
  const p = out[segIndex - 1];
683
703
  const q = out[segIndex];
684
704
  const manhattan = Math.abs(p.x - q.x) + Math.abs(p.y - q.y);
685
- if (manhattan < eps) return null;
705
+ if (manhattan < eps) {
706
+ return null;
707
+ }
686
708
  }
687
709
  if (segIndex + 2 <= out.length - 1) {
688
710
  const p = out[segIndex + 1];
689
711
  const q = out[segIndex + 2];
690
712
  const manhattan = Math.abs(p.x - q.x) + Math.abs(p.y - q.y);
691
- if (manhattan < eps) return null;
713
+ if (manhattan < eps) {
714
+ return null;
715
+ }
692
716
  }
693
717
  for (let i = 0; i < out.length - 1; i++) {
694
718
  const u = out[i];
695
719
  const v = out[i + 1];
696
- if (!isHorizontal(u, v, eps) && !isVertical(u, v, eps)) return null;
720
+ if (!isHorizontal(u, v, eps) && !isVertical(u, v, eps)) {
721
+ return null;
722
+ }
697
723
  }
698
724
  return out;
699
725
  };
700
- var pathKey = (pts, decimals = 6) => pts.map((p) => `${p.x.toFixed(decimals)},${p.y.toFixed(decimals)}`).join("|");
726
+ var pathKey = (pts, decimals = 6) => {
727
+ const key = pts.map((p) => `${p.x.toFixed(decimals)},${p.y.toFixed(decimals)}`).join("|");
728
+ return key;
729
+ };
701
730
 
702
731
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
703
732
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
@@ -776,10 +805,17 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
776
805
  return;
777
806
  }
778
807
  const { path, collisionChipIds } = state;
808
+ const [PA, PB] = this.pins;
779
809
  const collision = findFirstCollision(path, this.obstacles);
780
810
  if (!collision) {
781
- this.solvedTracePath = path;
782
- this.solved = true;
811
+ const first = path[0];
812
+ const last = path[path.length - 1];
813
+ const EPS4 = 1e-9;
814
+ const samePoint = (p, q) => Math.abs(p.x - q.x) < EPS4 && Math.abs(p.y - q.y) < EPS4;
815
+ if (samePoint(first, { x: PA.x, y: PA.y }) && samePoint(last, { x: PB.x, y: PB.y })) {
816
+ this.solvedTracePath = path;
817
+ this.solved = true;
818
+ }
783
819
  return;
784
820
  }
785
821
  let { segIndex, rect } = collision;
@@ -802,7 +838,6 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
802
838
  if (!axis) {
803
839
  return;
804
840
  }
805
- const [PA, PB] = this.pins;
806
841
  const candidates = [];
807
842
  if (collisionChipIds.size === 0) {
808
843
  const m1 = midBetweenPointAndRect(axis, { x: PA.x, y: PA.y }, rect);
@@ -980,12 +1015,12 @@ var applyJogToTerminalSegment = ({
980
1015
  if (si !== 0 && si !== pts.length - 2) return;
981
1016
  const start = pts[si];
982
1017
  const end = pts[si + 1];
983
- const isVertical2 = Math.abs(start.x - end.x) < EPS4;
984
- const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
985
- if (!isVertical2 && !isHorizontal2) return;
986
- const segDir = isVertical2 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1018
+ const isVertical3 = Math.abs(start.x - end.x) < EPS4;
1019
+ const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1020
+ if (!isVertical3 && !isHorizontal3) return;
1021
+ const segDir = isVertical3 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
987
1022
  if (si === 0) {
988
- if (isVertical2) {
1023
+ if (isVertical3) {
989
1024
  const jogY = start.y + segDir * JOG_SIZE;
990
1025
  pts.splice(
991
1026
  1,
@@ -1005,7 +1040,7 @@ var applyJogToTerminalSegment = ({
1005
1040
  );
1006
1041
  }
1007
1042
  } else {
1008
- if (isVertical2) {
1043
+ if (isVertical3) {
1009
1044
  const jogY = end.y - segDir * JOG_SIZE;
1010
1045
  pts.splice(
1011
1046
  si,
@@ -1085,10 +1120,10 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1085
1120
  } else {
1086
1121
  const start = pts[si];
1087
1122
  const end = pts[si + 1];
1088
- const isVertical2 = Math.abs(start.x - end.x) < EPS4;
1089
- const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
1090
- if (!isVertical2 && !isHorizontal2) continue;
1091
- if (isVertical2) {
1123
+ const isVertical3 = Math.abs(start.x - end.x) < EPS4;
1124
+ const isHorizontal3 = Math.abs(start.y - end.y) < EPS4;
1125
+ if (!isVertical3 && !isHorizontal3) continue;
1126
+ if (isVertical3) {
1092
1127
  start.x += offset;
1093
1128
  end.x += offset;
1094
1129
  } else {
@@ -2164,6 +2199,854 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2164
2199
  // lib/solvers/GuidelinesSolver/GuidelinesSolver.ts
2165
2200
  import { getBounds as getBounds3 } from "graphics-debug";
2166
2201
 
2202
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2203
+ var detectTraceLabelOverlap = (traces, netLabels) => {
2204
+ const overlaps = [];
2205
+ for (const trace of traces) {
2206
+ for (const label of netLabels) {
2207
+ const labelBounds = getRectBounds(label.center, label.width, label.height);
2208
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
2209
+ const p1 = trace.tracePath[i];
2210
+ const p2 = trace.tracePath[i + 1];
2211
+ if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2212
+ if (trace.globalConnNetId === label.globalConnNetId) {
2213
+ break;
2214
+ }
2215
+ overlaps.push({ trace, label });
2216
+ break;
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ return overlaps;
2222
+ };
2223
+
2224
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2225
+ var findTraceViolationZone = (path, labelBounds) => {
2226
+ const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2227
+ let firstInsideIndex = -1;
2228
+ let lastInsideIndex = -1;
2229
+ for (let i = 0; i < path.length; i++) {
2230
+ if (isPointInside(path[i])) {
2231
+ if (firstInsideIndex === -1) {
2232
+ firstInsideIndex = i;
2233
+ }
2234
+ lastInsideIndex = i;
2235
+ }
2236
+ }
2237
+ return { firstInsideIndex, lastInsideIndex };
2238
+ };
2239
+
2240
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts
2241
+ var simplifyPath = (path) => {
2242
+ if (path.length < 3) return path;
2243
+ const newPath = [path[0]];
2244
+ for (let i = 1; i < path.length - 1; i++) {
2245
+ const p1 = newPath[newPath.length - 1];
2246
+ const p2 = path[i];
2247
+ const p3 = path[i + 1];
2248
+ if (isVertical(p1, p2) && isVertical(p2, p3) || isHorizontal(p1, p2) && isHorizontal(p2, p3)) {
2249
+ continue;
2250
+ }
2251
+ newPath.push(p2);
2252
+ }
2253
+ newPath.push(path[path.length - 1]);
2254
+ if (newPath.length < 3) return newPath;
2255
+ const finalPath = [newPath[0]];
2256
+ for (let i = 1; i < newPath.length - 1; i++) {
2257
+ const p1 = finalPath[finalPath.length - 1];
2258
+ const p2 = newPath[i];
2259
+ const p3 = newPath[i + 1];
2260
+ if (isVertical(p1, p2) && isVertical(p2, p3) || isHorizontal(p1, p2) && isHorizontal(p2, p3)) {
2261
+ continue;
2262
+ }
2263
+ finalPath.push(p2);
2264
+ }
2265
+ finalPath.push(newPath[newPath.length - 1]);
2266
+ return finalPath;
2267
+ };
2268
+
2269
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2270
+ var trySnipAndReconnect = ({
2271
+ initialTrace,
2272
+ firstInsideIndex,
2273
+ lastInsideIndex,
2274
+ labelBounds,
2275
+ obstacles
2276
+ }) => {
2277
+ if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2278
+ return null;
2279
+ }
2280
+ const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2281
+ const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2282
+ const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2283
+ const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2284
+ const candidateDetours = [];
2285
+ if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
2286
+ candidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2287
+ candidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2288
+ } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
2289
+ const newPath = [...pathToEntry, ...pathFromExit];
2290
+ const simplified = simplifyPath(newPath);
2291
+ if (!isPathCollidingWithObstacles(simplified, obstacles)) {
2292
+ return { ...initialTrace, tracePath: simplified };
2293
+ }
2294
+ }
2295
+ for (let i = 0; i < candidateDetours.length; i++) {
2296
+ const detour = candidateDetours[i];
2297
+ const newFullPath = [...pathToEntry, ...detour, ...pathFromExit];
2298
+ const simplified = simplifyPath(newFullPath);
2299
+ if (!isPathCollidingWithObstacles(simplified, obstacles)) {
2300
+ return { ...initialTrace, tracePath: simplified };
2301
+ }
2302
+ }
2303
+ candidateDetours.length = 0;
2304
+ const buffer = 0.1;
2305
+ const leftX = labelBounds.minX - buffer;
2306
+ const rightX = labelBounds.maxX + buffer;
2307
+ const topY = labelBounds.maxY + buffer;
2308
+ const bottomY = labelBounds.minY - buffer;
2309
+ if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2310
+ candidateDetours.push([
2311
+ { x: leftX, y: entryPoint.y },
2312
+ { x: leftX, y: exitPoint.y }
2313
+ ]);
2314
+ }
2315
+ if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2316
+ candidateDetours.push([
2317
+ { x: rightX, y: entryPoint.y },
2318
+ { x: rightX, y: exitPoint.y }
2319
+ ]);
2320
+ }
2321
+ if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2322
+ candidateDetours.push([
2323
+ { x: entryPoint.x, y: topY },
2324
+ { x: exitPoint.x, y: topY }
2325
+ ]);
2326
+ }
2327
+ if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2328
+ candidateDetours.push([
2329
+ { x: entryPoint.x, y: bottomY },
2330
+ { x: exitPoint.x, y: bottomY }
2331
+ ]);
2332
+ }
2333
+ for (let i = 0; i < candidateDetours.length; i++) {
2334
+ const detour = candidateDetours[i];
2335
+ const newFullPath = [...pathToEntry, ...detour, ...pathFromExit];
2336
+ const simplified = simplifyPath(newFullPath);
2337
+ if (!isPathCollidingWithObstacles(simplified, obstacles)) {
2338
+ return { ...initialTrace, tracePath: simplified };
2339
+ }
2340
+ }
2341
+ return null;
2342
+ };
2343
+ var tryFourPointDetour = ({
2344
+ initialTrace,
2345
+ label,
2346
+ labelBounds,
2347
+ obstacles,
2348
+ paddingBuffer,
2349
+ detourCount
2350
+ }) => {
2351
+ let collidingSegIndex = -1;
2352
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2353
+ if (segmentIntersectsRect(
2354
+ initialTrace.tracePath[i],
2355
+ initialTrace.tracePath[i + 1],
2356
+ labelBounds
2357
+ )) {
2358
+ collidingSegIndex = i;
2359
+ break;
2360
+ }
2361
+ }
2362
+ if (collidingSegIndex === -1) return initialTrace;
2363
+ const pA = initialTrace.tracePath[collidingSegIndex];
2364
+ const pB = initialTrace.tracePath[collidingSegIndex + 1];
2365
+ if (!pA || !pB) return null;
2366
+ const candidateDetours = [];
2367
+ const paddedLabelBounds = getRectBounds(
2368
+ label.center,
2369
+ label.width,
2370
+ label.height
2371
+ );
2372
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2373
+ if (isVertical(pA, pB)) {
2374
+ const xCandidates = [
2375
+ paddedLabelBounds.maxX + effectivePadding,
2376
+ paddedLabelBounds.minX - effectivePadding
2377
+ ];
2378
+ for (const newX of xCandidates) {
2379
+ candidateDetours.push(
2380
+ pB.y > pA.y ? [
2381
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
2382
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2383
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2384
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
2385
+ ] : [
2386
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
2387
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2388
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2389
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
2390
+ ]
2391
+ );
2392
+ }
2393
+ } else {
2394
+ const yCandidates = [
2395
+ paddedLabelBounds.maxY + effectivePadding,
2396
+ paddedLabelBounds.minY - effectivePadding
2397
+ ];
2398
+ for (const newY of yCandidates) {
2399
+ candidateDetours.push(
2400
+ pB.x > pA.x ? [
2401
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
2402
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2403
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2404
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
2405
+ ] : [
2406
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
2407
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2408
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2409
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
2410
+ ]
2411
+ );
2412
+ }
2413
+ }
2414
+ for (const detourPoints of candidateDetours) {
2415
+ const finalPath = [
2416
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
2417
+ ...detourPoints,
2418
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1)
2419
+ ];
2420
+ const simplifiedFinalPath = simplifyPath(finalPath);
2421
+ if (!isPathCollidingWithObstacles(simplifiedFinalPath, obstacles)) {
2422
+ return { ...initialTrace, tracePath: simplifiedFinalPath };
2423
+ }
2424
+ }
2425
+ return null;
2426
+ };
2427
+
2428
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
2429
+ var rerouteCollidingTrace = ({
2430
+ trace,
2431
+ label,
2432
+ problem,
2433
+ paddingBuffer,
2434
+ detourCount
2435
+ }) => {
2436
+ const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
2437
+ if (trace.globalConnNetId === label.globalConnNetId) {
2438
+ return initialTrace;
2439
+ }
2440
+ const obstacles = getObstacleRects(problem);
2441
+ const labelPadding = paddingBuffer;
2442
+ const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
2443
+ const labelBounds = {
2444
+ minX: labelBoundsRaw.minX - labelPadding,
2445
+ minY: labelBoundsRaw.minY - labelPadding,
2446
+ maxX: labelBoundsRaw.maxX + labelPadding,
2447
+ maxY: labelBoundsRaw.maxY + labelPadding,
2448
+ chipId: `netlabel-${label.netId}`
2449
+ };
2450
+ const fourPointResult = tryFourPointDetour({
2451
+ initialTrace,
2452
+ label,
2453
+ labelBounds,
2454
+ obstacles,
2455
+ paddingBuffer,
2456
+ detourCount
2457
+ });
2458
+ if (fourPointResult) {
2459
+ initialTrace.tracePath = fourPointResult.tracePath;
2460
+ }
2461
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
2462
+ initialTrace.tracePath,
2463
+ labelBounds
2464
+ );
2465
+ const snipReconnectResult = trySnipAndReconnect({
2466
+ initialTrace,
2467
+ firstInsideIndex,
2468
+ lastInsideIndex,
2469
+ labelBounds,
2470
+ obstacles
2471
+ });
2472
+ if (snipReconnectResult) {
2473
+ return snipReconnectResult;
2474
+ }
2475
+ if (fourPointResult) {
2476
+ return fourPointResult;
2477
+ }
2478
+ return initialTrace;
2479
+ };
2480
+
2481
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisions.ts
2482
+ var hasCollisions = (pathSegments, obstacles) => {
2483
+ for (let i = 0; i < pathSegments.length - 1; i++) {
2484
+ const p1 = pathSegments[i];
2485
+ const p2 = pathSegments[i + 1];
2486
+ for (const obstacle of obstacles) {
2487
+ if (segmentIntersectsRect(p1, p2, obstacle)) {
2488
+ return true;
2489
+ }
2490
+ }
2491
+ }
2492
+ return false;
2493
+ };
2494
+
2495
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/countTurns.ts
2496
+ var countTurns = (points) => {
2497
+ let turns = 0;
2498
+ for (let i = 1; i < points.length - 1; i++) {
2499
+ const prev = points[i - 1];
2500
+ const curr = points[i];
2501
+ const next = points[i + 1];
2502
+ const prevVertical = prev.x === curr.x;
2503
+ const nextVertical = curr.x === next.x;
2504
+ if (prevVertical !== nextVertical) {
2505
+ turns++;
2506
+ }
2507
+ }
2508
+ return turns;
2509
+ };
2510
+
2511
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts
2512
+ var minimizeTurns = ({
2513
+ path,
2514
+ obstacles,
2515
+ labelBounds
2516
+ }) => {
2517
+ if (path.length <= 2) {
2518
+ return path;
2519
+ }
2520
+ const hasCollisionsWithLabels = (pathSegments, labels) => {
2521
+ for (let i = 0; i < pathSegments.length - 1; i++) {
2522
+ const p1 = pathSegments[i];
2523
+ const p2 = pathSegments[i + 1];
2524
+ for (const label of labels) {
2525
+ if (segmentIntersectsRect(p1, p2, label)) {
2526
+ return true;
2527
+ }
2528
+ }
2529
+ }
2530
+ return false;
2531
+ };
2532
+ const tryConnectPoints = (start, end) => {
2533
+ const candidates = [];
2534
+ if (start.x === end.x || start.y === end.y) {
2535
+ candidates.push([start, end]);
2536
+ } else {
2537
+ candidates.push([start, { x: end.x, y: start.y }, end]);
2538
+ candidates.push([start, { x: start.x, y: end.y }, end]);
2539
+ }
2540
+ return candidates;
2541
+ };
2542
+ const recognizeStairStepPattern = (pathToCheck, startIdx) => {
2543
+ if (startIdx >= pathToCheck.length - 3) return -1;
2544
+ let endIdx = startIdx;
2545
+ let isStairStep = true;
2546
+ for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
2547
+ if (i + 2 >= pathToCheck.length) break;
2548
+ const p1 = pathToCheck[i];
2549
+ const p2 = pathToCheck[i + 1];
2550
+ const p3 = pathToCheck[i + 2];
2551
+ const seg1Vertical = p1.x === p2.x;
2552
+ const seg2Vertical = p2.x === p3.x;
2553
+ if (seg1Vertical === seg2Vertical) {
2554
+ break;
2555
+ }
2556
+ const seg1Direction = seg1Vertical ? Math.sign(p2.y - p1.y) : Math.sign(p2.x - p1.x);
2557
+ if (i > startIdx) {
2558
+ const prevP = pathToCheck[i - 1];
2559
+ const prevSegVertical = prevP.x === p1.x;
2560
+ const prevDirection = prevSegVertical ? Math.sign(p1.y - prevP.y) : Math.sign(p1.x - prevP.x);
2561
+ if (seg1Vertical && prevSegVertical && seg1Direction !== prevDirection || !seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection) {
2562
+ isStairStep = false;
2563
+ break;
2564
+ }
2565
+ }
2566
+ endIdx = i + 2;
2567
+ }
2568
+ return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1;
2569
+ };
2570
+ let optimizedPath = [...path];
2571
+ let currentTurns = countTurns(optimizedPath);
2572
+ let improved = true;
2573
+ while (improved) {
2574
+ improved = false;
2575
+ for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
2576
+ const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx);
2577
+ if (stairEndIdx > 0) {
2578
+ const startPoint = optimizedPath[startIdx];
2579
+ const endPoint = optimizedPath[stairEndIdx];
2580
+ const connectionOptions = tryConnectPoints(startPoint, endPoint);
2581
+ for (const connection of connectionOptions) {
2582
+ const testPath = [
2583
+ ...optimizedPath.slice(0, startIdx + 1),
2584
+ ...connection.slice(1, -1),
2585
+ ...optimizedPath.slice(stairEndIdx)
2586
+ ];
2587
+ const collidesWithObstacles = hasCollisions(connection, obstacles);
2588
+ const collidesWithLabels = hasCollisionsWithLabels(
2589
+ connection,
2590
+ labelBounds
2591
+ );
2592
+ if (!collidesWithObstacles && !collidesWithLabels) {
2593
+ const newTurns = countTurns(testPath);
2594
+ const turnsRemoved = stairEndIdx - startIdx - 1;
2595
+ optimizedPath = testPath;
2596
+ currentTurns = newTurns;
2597
+ improved = true;
2598
+ break;
2599
+ }
2600
+ }
2601
+ if (improved) break;
2602
+ }
2603
+ }
2604
+ if (!improved) {
2605
+ for (let startIdx = 0; startIdx < optimizedPath.length - 2; startIdx++) {
2606
+ const maxRemove = Math.min(
2607
+ optimizedPath.length - startIdx - 2,
2608
+ optimizedPath.length - 2
2609
+ );
2610
+ for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
2611
+ const endIdx = startIdx + removeCount + 1;
2612
+ if (endIdx >= optimizedPath.length) continue;
2613
+ const startPoint = optimizedPath[startIdx];
2614
+ const endPoint = optimizedPath[endIdx];
2615
+ const connectionOptions = tryConnectPoints(startPoint, endPoint);
2616
+ for (const connection of connectionOptions) {
2617
+ const testPath = [
2618
+ ...optimizedPath.slice(0, startIdx + 1),
2619
+ ...connection.slice(1, -1),
2620
+ ...optimizedPath.slice(endIdx)
2621
+ ];
2622
+ const connectionSegments = connection;
2623
+ const collidesWithObstacles = hasCollisions(
2624
+ connectionSegments,
2625
+ obstacles
2626
+ );
2627
+ const collidesWithLabels = hasCollisionsWithLabels(
2628
+ connectionSegments,
2629
+ labelBounds
2630
+ );
2631
+ if (!collidesWithObstacles && !collidesWithLabels) {
2632
+ const newTurns = countTurns(testPath);
2633
+ if (newTurns < currentTurns || newTurns === currentTurns && testPath.length < optimizedPath.length) {
2634
+ optimizedPath = testPath;
2635
+ currentTurns = newTurns;
2636
+ improved = true;
2637
+ break;
2638
+ }
2639
+ }
2640
+ }
2641
+ if (improved) break;
2642
+ }
2643
+ if (improved) break;
2644
+ }
2645
+ }
2646
+ if (!improved) {
2647
+ for (let i = 0; i < optimizedPath.length - 2; i++) {
2648
+ const p1 = optimizedPath[i];
2649
+ const p2 = optimizedPath[i + 1];
2650
+ const p3 = optimizedPath[i + 2];
2651
+ const allVertical = p1.x === p2.x && p2.x === p3.x;
2652
+ const allHorizontal = p1.y === p2.y && p2.y === p3.y;
2653
+ if (allVertical || allHorizontal) {
2654
+ const testPath = [
2655
+ ...optimizedPath.slice(0, i + 1),
2656
+ ...optimizedPath.slice(i + 2)
2657
+ ];
2658
+ const collidesWithObstacles = hasCollisions([p1, p3], obstacles);
2659
+ const collidesWithLabels = hasCollisionsWithLabels(
2660
+ [p1, p3],
2661
+ labelBounds
2662
+ );
2663
+ if (!collidesWithObstacles && !collidesWithLabels) {
2664
+ optimizedPath = testPath;
2665
+ improved = true;
2666
+ break;
2667
+ }
2668
+ }
2669
+ }
2670
+ }
2671
+ }
2672
+ const finalSimplifiedPath = simplifyPath(optimizedPath);
2673
+ return finalSimplifiedPath;
2674
+ };
2675
+ var minimizeTurnsWithFilteredLabels = ({
2676
+ traces,
2677
+ problem,
2678
+ allLabelPlacements,
2679
+ mergedLabelNetIdMap,
2680
+ paddingBuffer
2681
+ }) => {
2682
+ let changesMade = false;
2683
+ const obstacles = getObstacleRects(problem);
2684
+ const newTraces = traces.map((trace) => {
2685
+ const originalPath = trace.tracePath;
2686
+ const filteredLabels = allLabelPlacements.filter((label) => {
2687
+ const originalNetIds = mergedLabelNetIdMap.get(label.globalConnNetId);
2688
+ if (originalNetIds) {
2689
+ return !originalNetIds.has(trace.globalConnNetId);
2690
+ }
2691
+ return label.globalConnNetId !== trace.globalConnNetId;
2692
+ });
2693
+ const labelBounds = filteredLabels.map((nl) => ({
2694
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
2695
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
2696
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
2697
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer
2698
+ }));
2699
+ const newPath = minimizeTurns({
2700
+ path: originalPath,
2701
+ obstacles,
2702
+ labelBounds
2703
+ });
2704
+ if (newPath.length !== originalPath.length || newPath.some(
2705
+ (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y
2706
+ )) {
2707
+ changesMade = true;
2708
+ }
2709
+ return {
2710
+ ...trace,
2711
+ tracePath: newPath
2712
+ };
2713
+ });
2714
+ if (changesMade) {
2715
+ return newTraces;
2716
+ } else {
2717
+ return null;
2718
+ }
2719
+ };
2720
+
2721
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts
2722
+ var balanceLShapes = ({
2723
+ traces,
2724
+ problem,
2725
+ allLabelPlacements
2726
+ }) => {
2727
+ const TOLERANCE = 1e-5;
2728
+ let changesMade = false;
2729
+ for (const trace of traces) {
2730
+ if (trace.tracePath.length === 4) {
2731
+ const [p0, p1, p2, p3] = trace.tracePath;
2732
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2733
+ const isVHVShape = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x;
2734
+ const isCollinearHorizontal = p0.y === p1.y && p1.y === p2.y && p2.y === p3.y;
2735
+ const isCollinearVertical = p0.x === p1.x && p1.x === p2.x && p2.x === p3.x;
2736
+ const isCollinear = isCollinearHorizontal || isCollinearVertical;
2737
+ let isSameDirection = false;
2738
+ if (isHVHShape) {
2739
+ isSameDirection = Math.sign(p1.x - p0.x) === Math.sign(p3.x - p2.x);
2740
+ } else if (isVHVShape) {
2741
+ isSameDirection = Math.sign(p1.y - p0.y) === Math.sign(p3.y - p2.y);
2742
+ }
2743
+ const isValidZShape = (isHVHShape || isVHVShape) && !isCollinear && isSameDirection;
2744
+ if (!isValidZShape) {
2745
+ return null;
2746
+ }
2747
+ }
2748
+ }
2749
+ const obstacles = getObstacleRects(problem).map((obs) => ({
2750
+ ...obs,
2751
+ minX: obs.minX + TOLERANCE,
2752
+ maxX: obs.maxX - TOLERANCE,
2753
+ minY: obs.minY + TOLERANCE,
2754
+ maxY: obs.maxY - TOLERANCE
2755
+ }));
2756
+ const segmentIntersectsAnyRect = (p1, p2, rects) => {
2757
+ for (const rect of rects) {
2758
+ if (segmentIntersectsRect(p1, p2, rect)) {
2759
+ return true;
2760
+ }
2761
+ }
2762
+ return false;
2763
+ };
2764
+ const getLabelBounds = (labels, traceNetId) => {
2765
+ const filteredLabels = labels.filter(
2766
+ (label) => label.globalConnNetId !== traceNetId
2767
+ );
2768
+ return filteredLabels.map((nl) => ({
2769
+ minX: nl.center.x - nl.width / 2 + TOLERANCE,
2770
+ maxX: nl.center.x + nl.width / 2 - TOLERANCE,
2771
+ minY: nl.center.y - nl.height / 2 + TOLERANCE,
2772
+ maxY: nl.center.y + nl.height / 2 - TOLERANCE
2773
+ }));
2774
+ };
2775
+ const newTraces = traces.map((trace) => {
2776
+ let newPath = [...trace.tracePath];
2777
+ if (newPath.length < 4) {
2778
+ return { ...trace };
2779
+ }
2780
+ const labelBounds = getLabelBounds(
2781
+ allLabelPlacements,
2782
+ trace.globalConnNetId
2783
+ );
2784
+ if (newPath.length === 4) {
2785
+ const [p0, p1, p2, p3] = newPath;
2786
+ let p1New, p2New;
2787
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2788
+ if (isHVHShape) {
2789
+ const idealX = (p0.x + p3.x) / 2;
2790
+ p1New = { x: idealX, y: p1.y };
2791
+ p2New = { x: idealX, y: p2.y };
2792
+ } else {
2793
+ const idealY = (p0.y + p3.y) / 2;
2794
+ p1New = { x: p1.x, y: idealY };
2795
+ p2New = { x: p2.x, y: idealY };
2796
+ }
2797
+ const collides = segmentIntersectsAnyRect(p0, p1New, obstacles) || segmentIntersectsAnyRect(p1New, p2New, obstacles) || segmentIntersectsAnyRect(p2New, p3, obstacles) || segmentIntersectsAnyRect(p0, p1New, labelBounds) || segmentIntersectsAnyRect(p1New, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3, labelBounds);
2798
+ if (!collides) {
2799
+ newPath[1] = p1New;
2800
+ newPath[2] = p2New;
2801
+ changesMade = true;
2802
+ }
2803
+ return { ...trace, tracePath: simplifyPath(newPath) };
2804
+ }
2805
+ for (let i = 1; i < newPath.length - 4; i++) {
2806
+ const p1 = newPath[i];
2807
+ const p2 = newPath[i + 1];
2808
+ const p3 = newPath[i + 2];
2809
+ const p4 = newPath[i + 3];
2810
+ const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y;
2811
+ const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x;
2812
+ const isCollinearHorizontal = p1.y === p2.y && p2.y === p3.y && p3.y === p4.y;
2813
+ const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x;
2814
+ const isCollinear = isCollinearHorizontal || isCollinearVertical;
2815
+ let isSameDirection = false;
2816
+ if (isHVHZShape) {
2817
+ isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x);
2818
+ } else if (isVHVZShape) {
2819
+ isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y);
2820
+ }
2821
+ const isValidZShape = (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection;
2822
+ if (!isValidZShape) {
2823
+ continue;
2824
+ }
2825
+ let p2New, p3New;
2826
+ const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
2827
+ const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
2828
+ if (Math.abs(len1Original - len2Original) < 1e-3) {
2829
+ continue;
2830
+ }
2831
+ if (isHVHZShape) {
2832
+ const idealX = (p1.x + p4.x) / 2;
2833
+ p2New = { x: idealX, y: p2.y };
2834
+ p3New = { x: idealX, y: p3.y };
2835
+ } else {
2836
+ const idealY = (p1.y + p4.y) / 2;
2837
+ p2New = { x: p2.x, y: idealY };
2838
+ p3New = { x: p3.x, y: idealY };
2839
+ }
2840
+ const collides = segmentIntersectsAnyRect(p1, p2New, obstacles) || segmentIntersectsAnyRect(p2New, p3New, obstacles) || segmentIntersectsAnyRect(p3New, p4, obstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
2841
+ if (!collides) {
2842
+ newPath[i + 1] = p2New;
2843
+ newPath[i + 2] = p3New;
2844
+ changesMade = true;
2845
+ i = 0;
2846
+ }
2847
+ }
2848
+ const finalSimplifiedPath = simplifyPath(newPath);
2849
+ return {
2850
+ ...trace,
2851
+ tracePath: finalSimplifiedPath
2852
+ };
2853
+ });
2854
+ if (changesMade) {
2855
+ return newTraces;
2856
+ } else {
2857
+ return null;
2858
+ }
2859
+ };
2860
+
2861
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
2862
+ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2863
+ problem;
2864
+ traces;
2865
+ netTempLabelPlacements;
2866
+ netLabelPlacements;
2867
+ updatedTraces;
2868
+ updatedTracesMap;
2869
+ mergedLabelNetIdMap;
2870
+ detourCountByLabel;
2871
+ PADDING_BUFFER = 0.1;
2872
+ constructor(solverInput) {
2873
+ super();
2874
+ this.problem = solverInput.inputProblem;
2875
+ this.traces = solverInput.traces;
2876
+ this.updatedTraces = [...solverInput.traces];
2877
+ this.updatedTracesMap = /* @__PURE__ */ new Map();
2878
+ this.mergedLabelNetIdMap = /* @__PURE__ */ new Map();
2879
+ this.detourCountByLabel = /* @__PURE__ */ new Map();
2880
+ const originalLabels = solverInput.netLabelPlacements;
2881
+ this.netLabelPlacements = originalLabels;
2882
+ if (!originalLabels || originalLabels.length === 0) {
2883
+ this.netTempLabelPlacements = [];
2884
+ return;
2885
+ }
2886
+ const labelGroups = /* @__PURE__ */ new Map();
2887
+ for (const p of originalLabels) {
2888
+ if (p.pinIds.length === 0) continue;
2889
+ const chipId = p.pinIds[0].split(".")[0];
2890
+ if (!chipId) continue;
2891
+ const key = `${chipId}-${p.orientation}`;
2892
+ if (!labelGroups.has(key)) {
2893
+ labelGroups.set(key, []);
2894
+ }
2895
+ labelGroups.get(key).push(p);
2896
+ }
2897
+ const finalPlacements = [];
2898
+ for (const [key, group] of labelGroups.entries()) {
2899
+ if (group.length <= 1) {
2900
+ finalPlacements.push(...group);
2901
+ continue;
2902
+ }
2903
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
2904
+ for (const p of group) {
2905
+ const bounds = getRectBounds(p.center, p.width, p.height);
2906
+ minX = Math.min(minX, bounds.minX);
2907
+ minY = Math.min(minY, bounds.minY);
2908
+ maxX = Math.max(maxX, bounds.maxX);
2909
+ maxY = Math.max(maxY, bounds.maxY);
2910
+ }
2911
+ const newWidth = maxX - minX;
2912
+ const newHeight = maxY - minY;
2913
+ const template = group[0];
2914
+ const syntheticId = `merged-group-${key}`;
2915
+ const originalNetIds = new Set(group.map((p) => p.globalConnNetId));
2916
+ this.mergedLabelNetIdMap.set(syntheticId, originalNetIds);
2917
+ finalPlacements.push({
2918
+ ...template,
2919
+ globalConnNetId: syntheticId,
2920
+ width: newWidth,
2921
+ height: newHeight,
2922
+ center: { x: minX + newWidth / 2, y: minY + newHeight / 2 },
2923
+ pinIds: [...new Set(group.flatMap((p) => p.pinIds))],
2924
+ mspConnectionPairIds: [
2925
+ ...new Set(group.flatMap((p) => p.mspConnectionPairIds))
2926
+ ]
2927
+ });
2928
+ }
2929
+ this.netTempLabelPlacements = finalPlacements;
2930
+ }
2931
+ _step() {
2932
+ if (!this.traces || this.traces.length === 0 || !this.netTempLabelPlacements || this.netTempLabelPlacements.length === 0) {
2933
+ this.solved = true;
2934
+ return;
2935
+ }
2936
+ this.detourCountByLabel.clear();
2937
+ const overlaps = detectTraceLabelOverlap(
2938
+ this.traces,
2939
+ this.netTempLabelPlacements
2940
+ );
2941
+ if (overlaps.length === 0) {
2942
+ this.solved = true;
2943
+ return;
2944
+ }
2945
+ const unfriendlyOverlaps = overlaps.filter((o) => {
2946
+ const originalNetIds = this.mergedLabelNetIdMap.get(
2947
+ o.label.globalConnNetId
2948
+ );
2949
+ if (originalNetIds) {
2950
+ return !originalNetIds.has(o.trace.globalConnNetId);
2951
+ }
2952
+ return o.trace.globalConnNetId !== o.label.globalConnNetId;
2953
+ });
2954
+ if (unfriendlyOverlaps.length === 0) {
2955
+ this.solved = true;
2956
+ return;
2957
+ }
2958
+ const updatedTracesMap = /* @__PURE__ */ new Map();
2959
+ for (const trace of this.traces) {
2960
+ updatedTracesMap.set(trace.mspPairId, trace);
2961
+ }
2962
+ const processedTraceIds = /* @__PURE__ */ new Set();
2963
+ for (const overlap of unfriendlyOverlaps) {
2964
+ if (processedTraceIds.has(overlap.trace.mspPairId)) {
2965
+ continue;
2966
+ }
2967
+ const currentTraceState = updatedTracesMap.get(overlap.trace.mspPairId);
2968
+ const labelId = overlap.label.globalConnNetId;
2969
+ const detourCount = this.detourCountByLabel.get(labelId) || 0;
2970
+ const newTrace = rerouteCollidingTrace({
2971
+ trace: currentTraceState,
2972
+ label: overlap.label,
2973
+ problem: this.problem,
2974
+ paddingBuffer: this.PADDING_BUFFER,
2975
+ detourCount
2976
+ });
2977
+ if (newTrace.tracePath !== currentTraceState.tracePath) {
2978
+ this.detourCountByLabel.set(labelId, detourCount + 1);
2979
+ }
2980
+ updatedTracesMap.set(currentTraceState.mspPairId, newTrace);
2981
+ processedTraceIds.add(currentTraceState.mspPairId);
2982
+ }
2983
+ this.updatedTraces = Array.from(updatedTracesMap.values());
2984
+ const minimizedTraces = minimizeTurnsWithFilteredLabels({
2985
+ traces: this.updatedTraces,
2986
+ problem: this.problem,
2987
+ allLabelPlacements: this.netTempLabelPlacements,
2988
+ // Use temp labels which include merged ones
2989
+ mergedLabelNetIdMap: this.mergedLabelNetIdMap,
2990
+ paddingBuffer: this.PADDING_BUFFER
2991
+ });
2992
+ if (minimizedTraces) {
2993
+ this.updatedTraces = minimizedTraces;
2994
+ }
2995
+ const balancedTraces = balanceLShapes({
2996
+ traces: this.updatedTraces,
2997
+ problem: this.problem,
2998
+ allLabelPlacements: this.netLabelPlacements
2999
+ });
3000
+ if (balancedTraces) {
3001
+ this.updatedTraces = balancedTraces;
3002
+ }
3003
+ this.updatedTracesMap.clear();
3004
+ for (const trace of this.updatedTraces) {
3005
+ this.updatedTracesMap.set(trace.mspPairId, trace);
3006
+ }
3007
+ const finalLabelPlacementSolver = new NetLabelPlacementSolver({
3008
+ inputProblem: this.problem,
3009
+ inputTraceMap: Object.fromEntries(this.updatedTracesMap)
3010
+ });
3011
+ finalLabelPlacementSolver.solve();
3012
+ this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements;
3013
+ this.solved = true;
3014
+ }
3015
+ getOutput() {
3016
+ return {
3017
+ traceMap: this.updatedTracesMap,
3018
+ netLabelPlacements: this.netLabelPlacements
3019
+ };
3020
+ }
3021
+ visualize() {
3022
+ const graphics = visualizeInputProblem(this.problem);
3023
+ if (!graphics.lines) graphics.lines = [];
3024
+ if (!graphics.circles) graphics.circles = [];
3025
+ if (!graphics.texts) graphics.texts = [];
3026
+ if (!graphics.rects) graphics.rects = [];
3027
+ for (const trace of Object.values(this.updatedTraces)) {
3028
+ graphics.lines.push({
3029
+ points: trace.tracePath,
3030
+ strokeColor: "purple"
3031
+ });
3032
+ }
3033
+ for (const p of this.netLabelPlacements) {
3034
+ graphics.rects.push({
3035
+ center: p.center,
3036
+ width: p.width,
3037
+ height: p.height,
3038
+ fill: getColorFromString(p.globalConnNetId, 0.35)
3039
+ });
3040
+ graphics.points.push({
3041
+ x: p.anchorPoint.x,
3042
+ y: p.anchorPoint.y,
3043
+ color: getColorFromString(p.globalConnNetId, 0.9)
3044
+ });
3045
+ }
3046
+ return graphics;
3047
+ }
3048
+ };
3049
+
2167
3050
  // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
2168
3051
  var correctPinsInsideChips = (problem) => {
2169
3052
  for (const chip of problem.chips) {
@@ -2230,6 +3113,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
2230
3113
  schematicTraceLinesSolver;
2231
3114
  traceOverlapShiftSolver;
2232
3115
  netLabelPlacementSolver;
3116
+ traceLabelOverlapAvoidanceSolver;
2233
3117
  startTimeOfPhase;
2234
3118
  endTimeOfPhase;
2235
3119
  timeSpentOnPhase;
@@ -2308,6 +3192,35 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
2308
3192
  onSolved: (_solver) => {
2309
3193
  }
2310
3194
  }
3195
+ ),
3196
+ definePipelineStep(
3197
+ "traceLabelOverlapAvoidanceSolver",
3198
+ TraceLabelOverlapAvoidanceSolver,
3199
+ (instance) => {
3200
+ const traceMap = instance.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
3201
+ instance.schematicTraceLinesSolver.solvedTracePaths.map((p) => [
3202
+ p.mspPairId,
3203
+ p
3204
+ ])
3205
+ );
3206
+ const traces = Object.values(traceMap);
3207
+ const netLabelPlacements = instance.netLabelPlacementSolver.netLabelPlacements;
3208
+ return [
3209
+ {
3210
+ inputProblem: instance.inputProblem,
3211
+ traces,
3212
+ netLabelPlacements
3213
+ }
3214
+ ];
3215
+ },
3216
+ {
3217
+ onSolved: (instance) => {
3218
+ if (instance.traceLabelOverlapAvoidanceSolver && instance.netLabelPlacementSolver) {
3219
+ const { netLabelPlacements } = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3220
+ instance.netLabelPlacementSolver.netLabelPlacements = netLabelPlacements;
3221
+ }
3222
+ }
3223
+ }
2311
3224
  )
2312
3225
  ];
2313
3226
  constructor(inputProblem) {