@tscircuit/schematic-trace-solver 0.0.124 → 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 (29) hide show
  1. package/dist/index.d.ts +46 -1
  2. package/dist/index.js +461 -37
  3. package/lib/solvers/AvailableNetOrientationSolver/geometry.ts +5 -1
  4. package/lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts +15 -0
  5. package/lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts +49 -6
  6. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +69 -11
  7. package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +216 -0
  8. package/lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts +186 -0
  9. package/lib/solvers/TraceElbowTransitionSimplificationSolver/types.ts +10 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +3 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +16 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +56 -13
  13. package/package.json +1 -1
  14. package/site/bug-reports/bug-report-20260805T061316Z.page.tsx +4 -0
  15. package/site/bug-reports/bug-report-20260806T061548Z.page.tsx +4 -0
  16. package/site/bug-reports/bug-report-20260806T093501Z.page.tsx +4 -0
  17. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +3 -3
  18. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +6 -6
  19. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +212 -0
  20. package/tests/bug-reports/bug-report-20260805T061316Z/bug-report-20260805T061316Z.json +889 -0
  21. package/tests/bug-reports/bug-report-20260805T061316Z/bug-report-20260805T061316Z.test.ts +12 -0
  22. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +212 -0
  23. package/tests/bug-reports/bug-report-20260806T061548Z/bug-report-20260806T061548Z.json +960 -0
  24. package/tests/bug-reports/bug-report-20260806T061548Z/bug-report-20260806T061548Z.test.ts +32 -0
  25. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +135 -0
  26. package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.json +505 -0
  27. package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.test.ts +66 -0
  28. package/tests/examples/__snapshots__/example01.snap.svg +18 -18
  29. 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) => {
@@ -9045,6 +9097,7 @@ var ensureGraphicsArrays2 = (graphics) => {
9045
9097
  };
9046
9098
 
9047
9099
  // lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts
9100
+ var LABEL_TRACE_CLEARANCE3 = 0.1;
9048
9101
  var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9049
9102
  inputProblem;
9050
9103
  traces;
@@ -9226,11 +9279,11 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9226
9279
  const anchorAlignedCandidates = [];
9227
9280
  const railAlignedCandidates = [];
9228
9281
  const labelTraces = this.getTraceLinesForLabel(label);
9229
- const allowRailAlignedFallback = this.isConfiguredRailLabel(label) && !labelTraces.some(
9282
+ const allowRailAlignedFallback = this.isConfiguredRailLabel(label) && (this.isLabelCrossedByOtherNetTrace(label) || !labelTraces.some(
9230
9283
  (trace) => getTraceCorners(trace.tracePath).some(
9231
9284
  (corner) => this.pointsEqual(corner, label.anchorPoint)
9232
9285
  )
9233
- );
9286
+ ));
9234
9287
  for (const trace of labelTraces) {
9235
9288
  const path = trace.tracePath;
9236
9289
  const pins = [path[0], path[path.length - 1]];
@@ -9269,6 +9322,13 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9269
9322
  (orientation) => orientation === "y+" || orientation === "y-"
9270
9323
  );
9271
9324
  }
9325
+ isLabelCrossedByOtherNetTrace(label) {
9326
+ const bounds = getRectBounds(label.center, label.width, label.height);
9327
+ const otherNetTraceMap = Object.fromEntries(
9328
+ this.traces.filter((trace) => trace.globalConnNetId !== label.globalConnNetId).map((trace) => [trace.mspPairId, trace])
9329
+ );
9330
+ return traceCrossesBoundsInterior(bounds, otherNetTraceMap);
9331
+ }
9272
9332
  pointsEqual(a, b) {
9273
9333
  return Math.abs(a.x - b.x) <= EPS6 && Math.abs(a.y - b.y) <= EPS6;
9274
9334
  }
@@ -9318,6 +9378,30 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9318
9378
  shiftedCoordinates.add(chipBounds.minX - label.width / 2);
9319
9379
  shiftedCoordinates.add(chipBounds.maxX + label.width / 2);
9320
9380
  }
9381
+ for (const otherTrace of this.traces) {
9382
+ if (otherTrace.globalConnNetId === label.globalConnNetId) continue;
9383
+ for (let i = 0; i < otherTrace.tracePath.length - 1; i++) {
9384
+ const start = otherTrace.tracePath[i];
9385
+ const end = otherTrace.tracePath[i + 1];
9386
+ if (!segmentCrossesBoundsInterior(start, end, labelBounds)) continue;
9387
+ const halfLabelWidth = label.width / 2;
9388
+ if (Math.abs(start.x - end.x) <= EPS6) {
9389
+ shiftedCoordinates.add(
9390
+ start.x - halfLabelWidth - LABEL_TRACE_CLEARANCE3
9391
+ );
9392
+ shiftedCoordinates.add(
9393
+ start.x + halfLabelWidth + LABEL_TRACE_CLEARANCE3
9394
+ );
9395
+ } else {
9396
+ shiftedCoordinates.add(
9397
+ Math.min(start.x, end.x) - halfLabelWidth - LABEL_TRACE_CLEARANCE3
9398
+ );
9399
+ shiftedCoordinates.add(
9400
+ Math.max(start.x, end.x) + halfLabelWidth + LABEL_TRACE_CLEARANCE3
9401
+ );
9402
+ }
9403
+ }
9404
+ }
9321
9405
  return [...shiftedCoordinates].flatMap((x) => {
9322
9406
  if (Math.abs(x - corner.x) <= EPS6) return [];
9323
9407
  const reroutedTracePath = simplifyPath(
@@ -10265,6 +10349,7 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10265
10349
  netLabelPlacements;
10266
10350
  outputTraces;
10267
10351
  outputNetLabelPlacements;
10352
+ completedReroutes = [];
10268
10353
  activeSubSolver = null;
10269
10354
  recentlyFailed = /* @__PURE__ */ new Set();
10270
10355
  detourCounts = /* @__PURE__ */ new Map();
@@ -10290,6 +10375,17 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10290
10375
  if (this.activeSubSolver.solved) {
10291
10376
  const solvedPath = this.activeSubSolver.solvedTracePath;
10292
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
+ });
10293
10389
  const idx = this.outputTraces.findIndex(
10294
10390
  (t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
10295
10391
  );
@@ -10352,7 +10448,8 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10352
10448
  problem: this.inputProblem,
10353
10449
  paddingBuffer: PADDING_BUFFER,
10354
10450
  detourCount,
10355
- tracesToAvoidOverlapping: this.outputTraces
10451
+ tracesToAvoidOverlapping: this.outputTraces,
10452
+ netLabelPlacements: this.outputNetLabelPlacements
10356
10453
  });
10357
10454
  }
10358
10455
  /**
@@ -10384,6 +10481,7 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
10384
10481
  getOutput() {
10385
10482
  return {
10386
10483
  traces: this.outputTraces,
10484
+ completedReroutes: this.completedReroutes,
10387
10485
  netLabelPlacements: this.outputNetLabelPlacements
10388
10486
  };
10389
10487
  }
@@ -10796,7 +10894,7 @@ var removeConsecutiveDuplicatePoints = (path) => {
10796
10894
  }
10797
10895
  return filteredPath;
10798
10896
  };
10799
- var getPathLength2 = (path) => {
10897
+ var getPathLength3 = (path) => {
10800
10898
  let pathLength = 0;
10801
10899
  for (let pointIndex = 0; pointIndex < path.length - 1; pointIndex++) {
10802
10900
  const startPoint = path[pointIndex];
@@ -10880,7 +10978,7 @@ var getPerimeterCandidates = ({
10880
10978
  );
10881
10979
  }
10882
10980
  return candidates.sort(
10883
- (firstPath, secondPath) => getPathLength2(firstPath) - getPathLength2(secondPath)
10981
+ (firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
10884
10982
  );
10885
10983
  };
10886
10984
  var getSegmentMidpoint = (startPoint, endPoint) => {
@@ -10985,7 +11083,7 @@ var getJunctionCandidates = ({
10985
11083
  }
10986
11084
  }
10987
11085
  return candidates.sort(
10988
- (firstPath, secondPath) => getPathLength2(firstPath) - getPathLength2(secondPath)
11086
+ (firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
10989
11087
  );
10990
11088
  };
10991
11089
  var pathCollidesWithObstacles = ({
@@ -11493,6 +11591,278 @@ orientation: ${label.orientation}`
11493
11591
  }
11494
11592
  };
11495
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
+
11496
11866
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
11497
11867
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
11498
11868
  return {
@@ -11523,6 +11893,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11523
11893
  netLabelTraceCollisionSolver;
11524
11894
  traceCleanupSolver2;
11525
11895
  netLabelNetLabelCollisionSolver;
11896
+ traceElbowTransitionSimplificationSolver;
11897
+ preAlignmentTraceElbowTransitionSimplificationSolver;
11898
+ finalTraceElbowTransitionSimplificationSolver;
11526
11899
  sameNetJunctionAlignmentSolver;
11527
11900
  startTimeOfPhase;
11528
11901
  endTimeOfPhase;
@@ -11644,8 +12017,24 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11644
12017
  ];
11645
12018
  }
11646
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
+ ),
11647
12036
  definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
11648
- const prevSolverOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
12037
+ const prevSolverOutput = instance.traceElbowTransitionSimplificationSolver.getOutput();
11649
12038
  const traces = prevSolverOutput.traces;
11650
12039
  const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
11651
12040
  return [
@@ -11747,11 +12136,27 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11747
12136
  }
11748
12137
  ]
11749
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
+ ),
11750
12155
  definePipelineStep(
11751
12156
  "traceCleanupSolver2",
11752
12157
  TraceCleanupSolver,
11753
12158
  (instance) => {
11754
- const collisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
12159
+ const collisionOutput = instance.preAlignmentTraceElbowTransitionSimplificationSolver.getOutput();
11755
12160
  const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
11756
12161
  return [
11757
12162
  {
@@ -11804,16 +12209,35 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11804
12209
  ];
11805
12210
  }
11806
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
+ ),
11807
12228
  definePipelineStep(
11808
12229
  "netLabelNetLabelCollisionSolver",
11809
12230
  NetLabelNetLabelCollisionSolver,
11810
- (instance) => [
11811
- {
11812
- inputProblem: instance.inputProblem,
11813
- traces: instance.netLabelTraceCollisionSolver.getOutput().traces,
11814
- netLabelPlacements: instance.netLabelTraceCollisionSolver.getOutput().netLabelPlacements
11815
- }
11816
- ]
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
+ }
11817
12241
  ),
11818
12242
  definePipelineStep(
11819
12243
  "sameNetJunctionAlignmentSolver",