@tscircuit/schematic-trace-solver 0.0.125 → 0.0.126

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 (18) hide show
  1. package/dist/index.d.ts +45 -1
  2. package/dist/index.js +427 -35
  3. package/lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts +15 -0
  4. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +69 -11
  5. package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +216 -0
  6. package/lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts +186 -0
  7. package/lib/solvers/TraceElbowTransitionSimplificationSolver/types.ts +10 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +3 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +16 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +56 -13
  11. package/package.json +1 -1
  12. package/site/bug-reports/bug-report-20260806T093501Z.page.tsx +4 -0
  13. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +3 -3
  14. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +6 -6
  15. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +135 -0
  16. package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.json +505 -0
  17. package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.test.ts +66 -0
  18. package/tests/examples/__snapshots__/example33.snap.svg +1 -1
package/dist/index.js CHANGED
@@ -4000,6 +4000,16 @@ var doesPathOverlapTraceStrokes = (path, traces) => {
4000
4000
 
4001
4001
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
4002
4002
  var MAX_TRIES = 5;
4003
+ var PATH_LENGTH_EPSILON = 1e-9;
4004
+ var getPathLength = (points) => {
4005
+ let length = 0;
4006
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex++) {
4007
+ const point = points[pointIndex];
4008
+ const nextPoint = points[pointIndex + 1];
4009
+ length += Math.abs(nextPoint.x - point.x) + Math.abs(nextPoint.y - point.y);
4010
+ }
4011
+ return length;
4012
+ };
4003
4013
  var SingleOverlapSolver = class extends BaseSolver {
4004
4014
  queuedCandidatePaths;
4005
4015
  solvedTracePath = null;
@@ -4008,32 +4018,47 @@ var SingleOverlapSolver = class extends BaseSolver {
4008
4018
  obstacles;
4009
4019
  label;
4010
4020
  tracesToAvoidOverlapping;
4021
+ netLabelPlacements;
4022
+ detourCount;
4011
4023
  _tried = 0;
4012
4024
  constructor(solverInput) {
4013
4025
  super();
4014
4026
  this.initialTrace = solverInput.trace;
4015
4027
  this.problem = solverInput.problem;
4016
4028
  this.label = solverInput.label;
4029
+ this.detourCount = solverInput.detourCount;
4017
4030
  this.tracesToAvoidOverlapping = (solverInput.tracesToAvoidOverlapping ?? []).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId);
4031
+ this.netLabelPlacements = solverInput.netLabelPlacements ?? [
4032
+ solverInput.label
4033
+ ];
4034
+ this.obstacles = getObstacleRects(this.problem);
4018
4035
  const effectivePadding = solverInput.paddingBuffer + solverInput.detourCount * solverInput.paddingBuffer;
4019
4036
  const candidates = generateRerouteCandidates({
4020
4037
  ...solverInput,
4021
4038
  paddingBuffer: effectivePadding
4022
4039
  // Use the calculated, larger padding
4023
4040
  });
4024
- const getPathLength3 = (pts) => {
4025
- let len = 0;
4026
- for (let i = 0; i < pts.length - 1; i++) {
4027
- const dx = pts[i + 1].x - pts[i].x;
4028
- const dy = pts[i + 1].y - pts[i].y;
4029
- len += Math.sqrt(dx * dx + dy * dy);
4030
- }
4031
- return len;
4032
- };
4033
- this.queuedCandidatePaths = candidates.sort(
4034
- (a, b) => getPathLength3(a) - getPathLength3(b)
4035
- );
4036
- this.obstacles = getObstacleRects(this.problem);
4041
+ const getLabelOverlapCount = (path) => detectTraceLabelOverlap({
4042
+ traces: [{ ...this.initialTrace, tracePath: path }],
4043
+ netLabels: this.netLabelPlacements
4044
+ }).length;
4045
+ const candidateByPath = /* @__PURE__ */ new Map();
4046
+ for (const candidate of candidates) {
4047
+ const simplifiedCandidate = simplifyPath(candidate);
4048
+ candidateByPath.set(
4049
+ simplifiedCandidate.map((point) => `${point.x},${point.y}`).join(";"),
4050
+ simplifiedCandidate
4051
+ );
4052
+ }
4053
+ this.queuedCandidatePaths = [...candidateByPath.values()].sort((a, b) => {
4054
+ const pathLengthDifference = getPathLength(a) - getPathLength(b);
4055
+ if (Math.abs(pathLengthDifference) >= PATH_LENGTH_EPSILON) {
4056
+ return pathLengthDifference;
4057
+ }
4058
+ const overlapCountDifference = getLabelOverlapCount(a) - getLabelOverlapCount(b);
4059
+ if (overlapCountDifference !== 0) return overlapCountDifference;
4060
+ return a.length - b.length;
4061
+ });
4037
4062
  }
4038
4063
  _step() {
4039
4064
  if (this.queuedCandidatePaths.length === 0 || this._tried >= MAX_TRIES) {
@@ -4047,7 +4072,16 @@ var SingleOverlapSolver = class extends BaseSolver {
4047
4072
  traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
4048
4073
  netLabels: [this.label]
4049
4074
  }).length > 0;
4050
- if (!stillOverlapsLabel && !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
4075
+ const initialPath = simplifyPath(this.initialTrace.tracePath);
4076
+ const initialLabelOverlaps = detectTraceLabelOverlap({
4077
+ traces: [{ ...this.initialTrace, tracePath: initialPath }],
4078
+ netLabels: this.netLabelPlacements
4079
+ });
4080
+ const candidateLabelOverlaps = detectTraceLabelOverlap({
4081
+ traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
4082
+ netLabels: this.netLabelPlacements
4083
+ });
4084
+ if (!stillOverlapsLabel && candidateLabelOverlaps.length <= initialLabelOverlaps.length && !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
4051
4085
  this.solvedTracePath = simplifiedPath;
4052
4086
  this.solved = true;
4053
4087
  }
@@ -4070,7 +4104,7 @@ var SingleOverlapSolver = class extends BaseSolver {
4070
4104
  height: this.label.height,
4071
4105
  fill: "rgba(255, 0, 0, 0.2)"
4072
4106
  });
4073
- if (this.queuedCandidatePaths.length > 0) {
4107
+ if (!this.solvedTracePath && this.queuedCandidatePaths.length > 0) {
4074
4108
  graphics.lines.push({
4075
4109
  points: this.queuedCandidatePaths[0],
4076
4110
  strokeColor: "orange"
@@ -4154,6 +4188,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
4154
4188
  allTraces;
4155
4189
  tracesToAvoidOverlapping;
4156
4190
  modifiedTraces = [];
4191
+ completedReroutes = [];
4157
4192
  PADDING_BUFFER = 0.1;
4158
4193
  detourCounts = /* @__PURE__ */ new Map();
4159
4194
  activeSubSolver = null;
@@ -4179,6 +4214,17 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
4179
4214
  if (this.activeSubSolver.solved) {
4180
4215
  const solvedPath = this.activeSubSolver.solvedTracePath;
4181
4216
  if (solvedPath) {
4217
+ this.completedReroutes.push({
4218
+ initialTrace: {
4219
+ ...this.activeSubSolver.initialTrace,
4220
+ tracePath: this.activeSubSolver.initialTrace.tracePath.map(
4221
+ (point) => ({ ...point })
4222
+ )
4223
+ },
4224
+ reroutedTracePath: solvedPath.map((point) => ({ ...point })),
4225
+ label: this.activeSubSolver.label,
4226
+ detourCount: this.activeSubSolver.detourCount
4227
+ });
4182
4228
  const traceIndex = this.allTraces.findIndex(
4183
4229
  (t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
4184
4230
  );
@@ -4251,7 +4297,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
4251
4297
  problem: this.inputProblem,
4252
4298
  paddingBuffer: this.PADDING_BUFFER,
4253
4299
  detourCount: detourCount2,
4254
- tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
4300
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
4301
+ netLabelPlacements: this.initialNetLabelPlacements
4255
4302
  });
4256
4303
  } else {
4257
4304
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
@@ -4287,7 +4334,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
4287
4334
  problem: this.inputProblem,
4288
4335
  paddingBuffer: this.PADDING_BUFFER,
4289
4336
  detourCount: detourCount2,
4290
- tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
4337
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
4338
+ netLabelPlacements: this.initialNetLabelPlacements
4291
4339
  });
4292
4340
  } else {
4293
4341
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
@@ -4303,7 +4351,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
4303
4351
  problem: this.inputProblem,
4304
4352
  paddingBuffer: this.PADDING_BUFFER,
4305
4353
  detourCount,
4306
- tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
4354
+ tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
4355
+ netLabelPlacements: this.initialNetLabelPlacements
4307
4356
  });
4308
4357
  }
4309
4358
  }
@@ -4440,6 +4489,9 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
4440
4489
  const solvedTraces = this.subSolvers.flatMap((s) => s.getOutput().allTraces);
4441
4490
  return {
4442
4491
  traces: [...this.cleanTraces, ...solvedTraces],
4492
+ completedReroutes: this.subSolvers.flatMap(
4493
+ (solver) => solver.completedReroutes
4494
+ ),
4443
4495
  netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
4444
4496
  };
4445
4497
  }
@@ -5527,7 +5579,7 @@ var segmentRunsAlongRectBoundary = (start, end, rect) => {
5527
5579
 
5528
5580
  // lib/solvers/Example28Solver/geometry.ts
5529
5581
  var getPathKey = (path) => path.map((point) => `${point.x},${point.y}`).join(";");
5530
- var getPathLength = (path) => {
5582
+ var getPathLength2 = (path) => {
5531
5583
  let length = 0;
5532
5584
  for (let i = 0; i < path.length - 1; i++) {
5533
5585
  length += Math.abs(path[i + 1].x - path[i].x) + Math.abs(path[i + 1].y - path[i].y);
@@ -5681,7 +5733,7 @@ var getTraceGeometryMetrics = (traces, allTraces) => ({
5681
5733
  ),
5682
5734
  visibleLength: getVisibleTraceLength(traces),
5683
5735
  pathLength: traces.reduce(
5684
- (sum, trace) => sum + getPathLength(trace.tracePath),
5736
+ (sum, trace) => sum + getPathLength2(trace.tracePath),
5685
5737
  0
5686
5738
  ),
5687
5739
  otherNetCrossings: countOtherNetCrossings(traces, allTraces)
@@ -6496,7 +6548,7 @@ var UntangleTraceSubsolver = class extends BaseSolver {
6496
6548
  candidate.traceId
6497
6549
  ).isColliding && (crossing.isInitialBundleCrossing || !candidate.collision.isColliding)
6498
6550
  ).sort(
6499
- (first, second) => Number(first.collision.isColliding) - Number(second.collision.isColliding) || getPathLength(first.path) - getPathLength(second.path)
6551
+ (first, second) => Number(first.collision.isColliding) - Number(second.collision.isColliding) || getPathLength2(first.path) - getPathLength2(second.path)
6500
6552
  );
6501
6553
  const bestCandidate = validCandidates[0];
6502
6554
  if (!bestCandidate) return false;
@@ -7405,7 +7457,7 @@ var scoreTracePath = ({
7405
7457
  labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
7406
7458
  traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
7407
7459
  chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
7408
- pathLength: getPathLength(tracePath)
7460
+ pathLength: getPathLength2(tracePath)
7409
7461
  };
7410
7462
  };
7411
7463
  var countTraceIntersections = (trace, outputTraces) => {
@@ -10297,6 +10349,7 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10297
10349
  netLabelPlacements;
10298
10350
  outputTraces;
10299
10351
  outputNetLabelPlacements;
10352
+ completedReroutes = [];
10300
10353
  activeSubSolver = null;
10301
10354
  recentlyFailed = /* @__PURE__ */ new Set();
10302
10355
  detourCounts = /* @__PURE__ */ new Map();
@@ -10322,6 +10375,17 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10322
10375
  if (this.activeSubSolver.solved) {
10323
10376
  const solvedPath = this.activeSubSolver.solvedTracePath;
10324
10377
  if (solvedPath) {
10378
+ this.completedReroutes.push({
10379
+ initialTrace: {
10380
+ ...this.activeSubSolver.initialTrace,
10381
+ tracePath: this.activeSubSolver.initialTrace.tracePath.map(
10382
+ (point) => ({ ...point })
10383
+ )
10384
+ },
10385
+ reroutedTracePath: solvedPath.map((point) => ({ ...point })),
10386
+ label: this.activeSubSolver.label,
10387
+ detourCount: this.activeSubSolver.detourCount
10388
+ });
10325
10389
  const idx = this.outputTraces.findIndex(
10326
10390
  (t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
10327
10391
  );
@@ -10384,7 +10448,8 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10384
10448
  problem: this.inputProblem,
10385
10449
  paddingBuffer: PADDING_BUFFER,
10386
10450
  detourCount,
10387
- tracesToAvoidOverlapping: this.outputTraces
10451
+ tracesToAvoidOverlapping: this.outputTraces,
10452
+ netLabelPlacements: this.outputNetLabelPlacements
10388
10453
  });
10389
10454
  }
10390
10455
  /**
@@ -10416,6 +10481,7 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10416
10481
  getOutput() {
10417
10482
  return {
10418
10483
  traces: this.outputTraces,
10484
+ completedReroutes: this.completedReroutes,
10419
10485
  netLabelPlacements: this.outputNetLabelPlacements
10420
10486
  };
10421
10487
  }
@@ -10828,7 +10894,7 @@ var removeConsecutiveDuplicatePoints = (path) => {
10828
10894
  }
10829
10895
  return filteredPath;
10830
10896
  };
10831
- var getPathLength2 = (path) => {
10897
+ var getPathLength3 = (path) => {
10832
10898
  let pathLength = 0;
10833
10899
  for (let pointIndex = 0; pointIndex < path.length - 1; pointIndex++) {
10834
10900
  const startPoint = path[pointIndex];
@@ -10912,7 +10978,7 @@ var getPerimeterCandidates = ({
10912
10978
  );
10913
10979
  }
10914
10980
  return candidates.sort(
10915
- (firstPath, secondPath) => getPathLength2(firstPath) - getPathLength2(secondPath)
10981
+ (firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
10916
10982
  );
10917
10983
  };
10918
10984
  var getSegmentMidpoint = (startPoint, endPoint) => {
@@ -11017,7 +11083,7 @@ var getJunctionCandidates = ({
11017
11083
  }
11018
11084
  }
11019
11085
  return candidates.sort(
11020
- (firstPath, secondPath) => getPathLength2(firstPath) - getPathLength2(secondPath)
11086
+ (firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
11021
11087
  );
11022
11088
  };
11023
11089
  var pathCollidesWithObstacles = ({
@@ -11525,6 +11591,278 @@ orientation: ${label.orientation}`
11525
11591
  }
11526
11592
  };
11527
11593
 
11594
+ // lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts
11595
+ var isSimpleFiveSegmentElbow = (path) => {
11596
+ const simplifiedPath = simplifyPath(path);
11597
+ if (simplifiedPath.length !== 6) return false;
11598
+ const segmentIsHorizontal = simplifiedPath.slice(0, -1).map((point, index) => isHorizontal(point, simplifiedPath[index + 1]));
11599
+ return segmentIsHorizontal.every(
11600
+ (isHorizontalSegment, index) => index === 0 || isHorizontalSegment !== segmentIsHorizontal[index - 1]
11601
+ );
11602
+ };
11603
+ var generateSegmentShiftCandidates = ({
11604
+ trace,
11605
+ label,
11606
+ paddingBuffer,
11607
+ detourCount
11608
+ }) => {
11609
+ if (trace.globalConnNetId === label.globalConnNetId) return [];
11610
+ const path = simplifyPath(trace.tracePath);
11611
+ if (!isSimpleFiveSegmentElbow(path)) return [];
11612
+ const labelBounds = getRectBounds(label.center, label.width, label.height);
11613
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
11614
+ const paddedLabelBounds = {
11615
+ minX: labelBounds.minX - effectivePadding,
11616
+ maxX: labelBounds.maxX + effectivePadding,
11617
+ minY: labelBounds.minY - effectivePadding,
11618
+ maxY: labelBounds.maxY + effectivePadding
11619
+ };
11620
+ const candidates = [];
11621
+ for (let segmentIndex = 1; segmentIndex < path.length - 2; segmentIndex++) {
11622
+ const segmentStart = path[segmentIndex];
11623
+ const segmentEnd = path[segmentIndex + 1];
11624
+ if (!segmentIntersectsRect(segmentStart, segmentEnd, labelBounds)) continue;
11625
+ const isHorizontalSegment = isHorizontal(segmentStart, segmentEnd);
11626
+ const isVerticalSegment = isVertical(segmentStart, segmentEnd);
11627
+ if (!isHorizontalSegment && !isVerticalSegment) continue;
11628
+ const axis = isHorizontalSegment ? "y" : "x";
11629
+ const coordinates = isHorizontalSegment ? [paddedLabelBounds.minY, paddedLabelBounds.maxY] : [paddedLabelBounds.minX, paddedLabelBounds.maxX];
11630
+ for (const coordinate of coordinates) {
11631
+ const shiftedPath = shiftSegmentOrth(path, segmentIndex, axis, coordinate);
11632
+ if (shiftedPath) candidates.push(shiftedPath);
11633
+ }
11634
+ }
11635
+ return candidates;
11636
+ };
11637
+ var generateTransitionShiftCandidates = ({
11638
+ trace,
11639
+ label,
11640
+ paddingBuffer,
11641
+ detourCount
11642
+ }) => {
11643
+ const path = simplifyPath(trace.tracePath);
11644
+ if (!isSimpleFiveSegmentElbow(path)) return [];
11645
+ const labelBounds = getRectBounds(label.center, label.width, label.height);
11646
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
11647
+ const paddedLabelBounds = {
11648
+ minX: labelBounds.minX - effectivePadding,
11649
+ maxX: labelBounds.maxX + effectivePadding,
11650
+ minY: labelBounds.minY - effectivePadding,
11651
+ maxY: labelBounds.maxY + effectivePadding
11652
+ };
11653
+ const start = path[0];
11654
+ const end = path[path.length - 1];
11655
+ const middleSegmentIndex = 2;
11656
+ const candidates = [];
11657
+ for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) {
11658
+ const segmentStart = path[segmentIndex];
11659
+ const segmentEnd = path[segmentIndex + 1];
11660
+ if (!segmentIntersectsRect(segmentStart, segmentEnd, labelBounds)) continue;
11661
+ if (Math.abs(segmentIndex - middleSegmentIndex) !== 1) continue;
11662
+ const isHorizontalSegment = isHorizontal(segmentStart, segmentEnd);
11663
+ const isVerticalSegment = isVertical(segmentStart, segmentEnd);
11664
+ if (!isHorizontalSegment && !isVerticalSegment) continue;
11665
+ const axis = isHorizontalSegment ? "x" : "y";
11666
+ const min = isHorizontalSegment ? paddedLabelBounds.minX : paddedLabelBounds.minY;
11667
+ const max = isHorizontalSegment ? paddedLabelBounds.maxX : paddedLabelBounds.maxY;
11668
+ const startCoordinate = isHorizontalSegment ? start.x : start.y;
11669
+ const endCoordinate = isHorizontalSegment ? end.x : end.y;
11670
+ const corridorCoordinates = [];
11671
+ if (startCoordinate < min) {
11672
+ corridorCoordinates.push((startCoordinate + min) / 2);
11673
+ } else if (startCoordinate > max) {
11674
+ corridorCoordinates.push((startCoordinate + max) / 2);
11675
+ }
11676
+ if (endCoordinate < min) {
11677
+ corridorCoordinates.push((endCoordinate + min) / 2);
11678
+ } else if (endCoordinate > max) {
11679
+ corridorCoordinates.push((endCoordinate + max) / 2);
11680
+ }
11681
+ for (const coordinate of new Set(corridorCoordinates)) {
11682
+ const shiftedPath = shiftSegmentOrth(
11683
+ path,
11684
+ middleSegmentIndex,
11685
+ axis,
11686
+ coordinate
11687
+ );
11688
+ if (shiftedPath) candidates.push(shiftedPath);
11689
+ }
11690
+ }
11691
+ return candidates;
11692
+ };
11693
+ var generateElbowTransitionSimplificationCandidates = ({
11694
+ trace,
11695
+ label,
11696
+ netLabelPlacements,
11697
+ paddingBuffer,
11698
+ detourCount
11699
+ }) => {
11700
+ const transitionCandidates = generateTransitionShiftCandidates({
11701
+ trace,
11702
+ label,
11703
+ paddingBuffer,
11704
+ detourCount
11705
+ });
11706
+ return transitionCandidates.flatMap((tracePath) => {
11707
+ const shiftedTrace = { ...trace, tracePath };
11708
+ const shiftedOverlaps = detectTraceLabelOverlap({
11709
+ traces: [shiftedTrace],
11710
+ netLabels: netLabelPlacements
11711
+ });
11712
+ return shiftedOverlaps.flatMap(
11713
+ ({ label: shiftedLabel }) => generateSegmentShiftCandidates({
11714
+ trace: shiftedTrace,
11715
+ label: shiftedLabel,
11716
+ paddingBuffer,
11717
+ detourCount
11718
+ })
11719
+ );
11720
+ });
11721
+ };
11722
+
11723
+ // lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts
11724
+ var PATH_LENGTH_EPSILON2 = 1e-9;
11725
+ var getPathLength4 = (points) => points.slice(1).reduce((length, point, pointIndex) => {
11726
+ const previousPoint = points[pointIndex];
11727
+ return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
11728
+ }, 0);
11729
+ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
11730
+ input;
11731
+ outputTraces;
11732
+ traceIdQueue;
11733
+ obstacles;
11734
+ constructor(input) {
11735
+ super();
11736
+ this.input = input;
11737
+ this.outputTraces = [...input.traces];
11738
+ const reroutedTraceIds = new Set(
11739
+ input.completedReroutes.map(
11740
+ (completedReroute) => completedReroute.initialTrace.mspPairId
11741
+ )
11742
+ );
11743
+ this.traceIdQueue = input.traces.map((trace) => trace.mspPairId).filter((traceId) => reroutedTraceIds.has(traceId));
11744
+ this.obstacles = getObstacleRects(input.inputProblem);
11745
+ }
11746
+ _step() {
11747
+ const traceId = this.traceIdQueue.shift();
11748
+ if (!traceId) {
11749
+ this.solved = true;
11750
+ return;
11751
+ }
11752
+ const traceIndex = this.outputTraces.findIndex(
11753
+ (trace2) => trace2.mspPairId === traceId
11754
+ );
11755
+ const trace = this.outputTraces[traceIndex];
11756
+ const tracePath = simplifyPath(trace.tracePath);
11757
+ const initialOverlaps = detectTraceLabelOverlap({
11758
+ traces: [{ ...trace, tracePath }],
11759
+ netLabels: this.input.netLabelPlacements
11760
+ });
11761
+ const otherNetTraces = this.outputTraces.filter(
11762
+ (otherTrace) => otherTrace.mspPairId !== trace.mspPairId && otherTrace.globalConnNetId !== trace.globalConnNetId
11763
+ );
11764
+ const candidateByPath = /* @__PURE__ */ new Map();
11765
+ const completedReroutes = this.input.completedReroutes.filter(
11766
+ (completedReroute) => completedReroute.initialTrace.mspPairId === trace.mspPairId
11767
+ );
11768
+ for (const completedReroute of completedReroutes) {
11769
+ const initialReroutePath = simplifyPath(
11770
+ completedReroute.initialTrace.tracePath
11771
+ );
11772
+ const reroutedPath = simplifyPath(completedReroute.reroutedTracePath);
11773
+ const initialRerouteOverlapCount = detectTraceLabelOverlap({
11774
+ traces: [
11775
+ { ...completedReroute.initialTrace, tracePath: initialReroutePath }
11776
+ ],
11777
+ netLabels: this.input.netLabelPlacements
11778
+ }).length;
11779
+ const candidates = generateElbowTransitionSimplificationCandidates({
11780
+ trace: completedReroute.initialTrace,
11781
+ label: completedReroute.label,
11782
+ netLabelPlacements: this.input.netLabelPlacements,
11783
+ paddingBuffer: this.input.paddingBuffer,
11784
+ detourCount: completedReroute.detourCount
11785
+ });
11786
+ for (const candidate of candidates) {
11787
+ const simplifiedCandidate = simplifyPath(candidate);
11788
+ const candidateTrace = {
11789
+ ...completedReroute.initialTrace,
11790
+ tracePath: simplifiedCandidate
11791
+ };
11792
+ const candidateOverlapCount = detectTraceLabelOverlap({
11793
+ traces: [candidateTrace],
11794
+ netLabels: this.input.netLabelPlacements
11795
+ }).length;
11796
+ const isSimplerEquivalentReroute = candidateOverlapCount < initialRerouteOverlapCount && Math.abs(
11797
+ getPathLength4(simplifiedCandidate) - getPathLength4(reroutedPath)
11798
+ ) < PATH_LENGTH_EPSILON2 && simplifiedCandidate.length < reroutedPath.length && preservesLabelAnchors(
11799
+ this.input.netLabelPlacements,
11800
+ [completedReroute.initialTrace],
11801
+ [candidateTrace]
11802
+ );
11803
+ if (isSimplerEquivalentReroute) {
11804
+ candidateByPath.set(
11805
+ simplifiedCandidate.map((point) => `${point.x},${point.y}`).join(";"),
11806
+ simplifiedCandidate
11807
+ );
11808
+ }
11809
+ }
11810
+ }
11811
+ const initialOverlapIds = new Set(
11812
+ initialOverlaps.map(
11813
+ ({ label }) => `${label.globalConnNetId}:${label.netId}`
11814
+ )
11815
+ );
11816
+ const initialPathLength = getPathLength4(tracePath);
11817
+ const validCandidates = [...candidateByPath.values()].filter(
11818
+ (candidatePath) => {
11819
+ const candidateTrace = { ...trace, tracePath: candidatePath };
11820
+ const candidateOverlaps = detectTraceLabelOverlap({
11821
+ traces: [candidateTrace],
11822
+ netLabels: this.input.netLabelPlacements
11823
+ });
11824
+ const candidateOnlyKeepsExistingOverlaps = candidateOverlaps.every(
11825
+ ({ label }) => initialOverlapIds.has(`${label.globalConnNetId}:${label.netId}`)
11826
+ );
11827
+ const reducesCollisions = candidateOverlaps.length < initialOverlaps.length;
11828
+ const simplifiesGeometry = candidateOverlaps.length === initialOverlaps.length && getPathLength4(candidatePath) <= initialPathLength + PATH_LENGTH_EPSILON2 && candidatePath.length < tracePath.length;
11829
+ return candidateOnlyKeepsExistingOverlaps && (reducesCollisions || simplifiesGeometry) && preservesLabelAnchors(
11830
+ this.input.netLabelPlacements,
11831
+ [trace],
11832
+ [candidateTrace]
11833
+ ) && !isPathCollidingWithObstacles(candidatePath, this.obstacles) && !doesPathCoincideWithTraces(candidatePath, otherNetTraces);
11834
+ }
11835
+ );
11836
+ const getOverlapCount = (candidatePath) => detectTraceLabelOverlap({
11837
+ traces: [{ ...trace, tracePath: candidatePath }],
11838
+ netLabels: this.input.netLabelPlacements
11839
+ }).length;
11840
+ validCandidates.sort((a, b) => {
11841
+ const overlapDifference = getOverlapCount(a) - getOverlapCount(b);
11842
+ if (overlapDifference !== 0) return overlapDifference;
11843
+ return getPathLength4(a) - getPathLength4(b) || a.length - b.length;
11844
+ });
11845
+ const bestCandidate = validCandidates[0];
11846
+ if (!bestCandidate) return;
11847
+ this.outputTraces[traceIndex] = { ...trace, tracePath: bestCandidate };
11848
+ this.stats.simplifiedTraceCount = (this.stats.simplifiedTraceCount ?? 0) + 1;
11849
+ }
11850
+ getOutput() {
11851
+ return {
11852
+ traces: this.outputTraces,
11853
+ netLabelPlacements: this.input.netLabelPlacements
11854
+ };
11855
+ }
11856
+ visualize() {
11857
+ const graphics = visualizeInputProblem(this.input.inputProblem);
11858
+ graphics.lines ??= [];
11859
+ for (const trace of this.outputTraces) {
11860
+ graphics.lines.push({ points: trace.tracePath, strokeColor: "purple" });
11861
+ }
11862
+ return graphics;
11863
+ }
11864
+ };
11865
+
11528
11866
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
11529
11867
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
11530
11868
  return {
@@ -11555,6 +11893,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11555
11893
  netLabelTraceCollisionSolver;
11556
11894
  traceCleanupSolver2;
11557
11895
  netLabelNetLabelCollisionSolver;
11896
+ traceElbowTransitionSimplificationSolver;
11897
+ preAlignmentTraceElbowTransitionSimplificationSolver;
11898
+ finalTraceElbowTransitionSimplificationSolver;
11558
11899
  sameNetJunctionAlignmentSolver;
11559
11900
  startTimeOfPhase;
11560
11901
  endTimeOfPhase;
@@ -11676,8 +12017,24 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11676
12017
  ];
11677
12018
  }
11678
12019
  ),
12020
+ definePipelineStep(
12021
+ "traceElbowTransitionSimplificationSolver",
12022
+ TraceElbowTransitionSimplificationSolver,
12023
+ (instance) => {
12024
+ const overlapAvoidanceOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
12025
+ return [
12026
+ {
12027
+ inputProblem: instance.inputProblem,
12028
+ traces: overlapAvoidanceOutput.traces,
12029
+ completedReroutes: overlapAvoidanceOutput.completedReroutes,
12030
+ netLabelPlacements: instance.traceLabelOverlapAvoidanceSolver.netLabelPlacements,
12031
+ paddingBuffer: 0.1
12032
+ }
12033
+ ];
12034
+ }
12035
+ ),
11679
12036
  definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
11680
- const prevSolverOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
12037
+ const prevSolverOutput = instance.traceElbowTransitionSimplificationSolver.getOutput();
11681
12038
  const traces = prevSolverOutput.traces;
11682
12039
  const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
11683
12040
  return [
@@ -11779,11 +12136,27 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11779
12136
  }
11780
12137
  ]
11781
12138
  ),
12139
+ definePipelineStep(
12140
+ "preAlignmentTraceElbowTransitionSimplificationSolver",
12141
+ TraceElbowTransitionSimplificationSolver,
12142
+ (instance) => {
12143
+ const collisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
12144
+ return [
12145
+ {
12146
+ inputProblem: instance.inputProblem,
12147
+ traces: collisionOutput.traces,
12148
+ completedReroutes: collisionOutput.completedReroutes,
12149
+ netLabelPlacements: collisionOutput.netLabelPlacements,
12150
+ paddingBuffer: 0.1
12151
+ }
12152
+ ];
12153
+ }
12154
+ ),
11782
12155
  definePipelineStep(
11783
12156
  "traceCleanupSolver2",
11784
12157
  TraceCleanupSolver,
11785
12158
  (instance) => {
11786
- const collisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
12159
+ const collisionOutput = instance.preAlignmentTraceElbowTransitionSimplificationSolver.getOutput();
11787
12160
  const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
11788
12161
  return [
11789
12162
  {
@@ -11836,16 +12209,35 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11836
12209
  ];
11837
12210
  }
11838
12211
  ),
12212
+ definePipelineStep(
12213
+ "finalTraceElbowTransitionSimplificationSolver",
12214
+ TraceElbowTransitionSimplificationSolver,
12215
+ (instance) => {
12216
+ const collisionOutput = instance.netLabelTraceCollisionSolver.getOutput();
12217
+ return [
12218
+ {
12219
+ inputProblem: instance.inputProblem,
12220
+ traces: collisionOutput.traces,
12221
+ completedReroutes: collisionOutput.completedReroutes,
12222
+ netLabelPlacements: collisionOutput.netLabelPlacements,
12223
+ paddingBuffer: 0.1
12224
+ }
12225
+ ];
12226
+ }
12227
+ ),
11839
12228
  definePipelineStep(
11840
12229
  "netLabelNetLabelCollisionSolver",
11841
12230
  NetLabelNetLabelCollisionSolver,
11842
- (instance) => [
11843
- {
11844
- inputProblem: instance.inputProblem,
11845
- traces: instance.netLabelTraceCollisionSolver.getOutput().traces,
11846
- netLabelPlacements: instance.netLabelTraceCollisionSolver.getOutput().netLabelPlacements
11847
- }
11848
- ]
12231
+ (instance) => {
12232
+ const simplificationOutput = instance.finalTraceElbowTransitionSimplificationSolver.getOutput();
12233
+ return [
12234
+ {
12235
+ inputProblem: instance.inputProblem,
12236
+ traces: simplificationOutput.traces,
12237
+ netLabelPlacements: simplificationOutput.netLabelPlacements
12238
+ }
12239
+ ];
12240
+ }
11849
12241
  ),
11850
12242
  definePipelineStep(
11851
12243
  "sameNetJunctionAlignmentSolver",