@tscircuit/schematic-trace-solver 0.0.120 → 0.0.122

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 (40) hide show
  1. package/dist/index.d.ts +10 -3
  2. package/dist/index.js +350 -78
  3. package/lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts +223 -21
  4. package/lib/solvers/RailNetLabelCornerPlacementSolver/types.ts +3 -3
  5. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +34 -9
  6. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/evaluateRailGroup.ts +79 -59
  7. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getRailAlignmentFallbackCoordinates.ts +96 -0
  8. package/lib/utils/doesPathCoincideWithTraces.ts +71 -1
  9. package/package.json +1 -1
  10. package/site/bug-reports/bug-report-20260804T095800Z.page.tsx +4 -0
  11. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +4 -4
  12. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +5 -5
  13. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +12 -12
  14. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  15. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +135 -0
  17. package/tests/bug-reports/bug-report-20260804T095800Z/bug-report-20260804T095800Z.json +634 -0
  18. package/tests/bug-reports/bug-report-20260804T095800Z/bug-report-20260804T095800Z.test.ts +12 -0
  19. package/tests/examples/__snapshots__/example12.snap.svg +2 -2
  20. package/tests/examples/__snapshots__/example13.snap.svg +4 -4
  21. package/tests/examples/__snapshots__/example14.snap.svg +36 -36
  22. package/tests/examples/__snapshots__/example31.snap.svg +6 -6
  23. package/tests/examples/__snapshots__/example43.snap.svg +2 -2
  24. package/tests/examples/__snapshots__/example44.snap.svg +2 -2
  25. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
  26. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +26 -26
  27. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
  28. package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +2 -2
  29. package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +2 -2
  30. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +101 -0
  31. package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +18 -18
  32. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +3 -3
  33. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +82 -0
  34. package/tests/repros/assets/repro-bq25895-cross-net-trace-overlap.input.json +498 -0
  35. package/tests/repros/assets/repro-nrf52810-clock-routing.input.json +234 -0
  36. package/tests/repros/repro-bq25895-cross-net-trace-overlap.test.ts +88 -0
  37. package/tests/repros/repro-missing-trace-netlabel.test.ts +12 -0
  38. package/tests/repros/repro-netlabel-overlap-trace.test.ts +38 -0
  39. package/tests/repros/repro-nrf52810-clock-routing.test.ts +28 -0
  40. package/tests/solvers/TraceCleanupSolver/alignSameNetRails-trace-clearance.test.ts +68 -0
package/dist/index.d.ts CHANGED
@@ -828,11 +828,11 @@ type TraceCornerCandidate = {
828
828
  traceId: string;
829
829
  distance: number;
830
830
  /**
831
- * True when the corner lines up with one of the trace's pins along the
832
- * label's stub axis (same x for a vertical rail label), so the label reads as
833
- * a clean stub off that pin rather than floating over a mid-trace bend.
831
+ * True when the corner is on a vertical rail that connects directly to one
832
+ * of the trace's endpoint pins, rather than on an arbitrary middle bend.
834
833
  */
835
834
  pinAligned: boolean;
835
+ reroutedTracePath?: Point[];
836
836
  };
837
837
  type CornerCandidateStatus = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
838
838
  type EvaluatedCornerCandidate = TraceCornerCandidate & {
@@ -859,6 +859,7 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
859
859
  getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
860
860
  _step(): void;
861
861
  getOutput(): {
862
+ traces: SolvedTracePath[];
862
863
  netLabelPlacements: NetLabelPlacement[];
863
864
  };
864
865
  private getProcessableLabelIndices;
@@ -873,6 +874,12 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
873
874
  private intersectsAnyChip;
874
875
  private intersectsAnyOtherNetLabel;
875
876
  private getCornerCandidatesForLabel;
877
+ private isConfiguredRailLabel;
878
+ private pointsEqual;
879
+ private getVerticalSegmentPointIndices;
880
+ private isPinAlignedCorner;
881
+ private getClearanceShiftedCornerCandidates;
882
+ private isReroutedTraceClear;
876
883
  private getTraceLinesForLabel;
877
884
  private isTraceForLabel;
878
885
  visualize(): GraphicsObject;
package/dist/index.js CHANGED
@@ -3922,7 +3922,12 @@ var generateRerouteCandidates = ({
3922
3922
  };
3923
3923
 
3924
3924
  // lib/utils/doesPathCoincideWithTraces.ts
3925
+ import { boundsIntersection } from "@tscircuit/math-utils";
3925
3926
  var COINCIDENT_EPS = 2e-3;
3927
+ var GEOMETRY_EPS = 1e-6;
3928
+ var SCHEMATIC_TRACE_STROKE_WIDTH = 0.02;
3929
+ var SCHEMATIC_TRACE_MIN_CENTERLINE_CLEARANCE = SCHEMATIC_TRACE_STROKE_WIDTH + GEOMETRY_EPS;
3930
+ var SCHEMATIC_TRACE_MIN_VISUAL_CENTERLINE_CLEARANCE = SCHEMATIC_TRACE_STROKE_WIDTH * 3;
3926
3931
  var doesPathCoincideWithTraces = (path, traces) => {
3927
3932
  const rangesOverlap1D = (a1, a2, b1, b2) => Math.min(Math.max(a1, a2), Math.max(b1, b2)) - Math.max(Math.min(a1, a2), Math.min(b1, b2)) > COINCIDENT_EPS;
3928
3933
  for (let i = 0; i < path.length - 1; i++) {
@@ -3954,6 +3959,44 @@ var doesPathCoincideWithTraces = (path, traces) => {
3954
3959
  }
3955
3960
  return false;
3956
3961
  };
3962
+ var doesPathOverlapTraceStrokes = (path, traces) => {
3963
+ const traceStrokeRadius = SCHEMATIC_TRACE_MIN_CENTERLINE_CLEARANCE / 2;
3964
+ const getStrokeBounds = (start, end, isVertical5) => isVertical5 ? {
3965
+ minX: start.x - traceStrokeRadius,
3966
+ maxX: start.x + traceStrokeRadius,
3967
+ minY: Math.min(start.y, end.y),
3968
+ maxY: Math.max(start.y, end.y)
3969
+ } : {
3970
+ minX: Math.min(start.x, end.x),
3971
+ maxX: Math.max(start.x, end.x),
3972
+ minY: start.y - traceStrokeRadius,
3973
+ maxY: start.y + traceStrokeRadius
3974
+ };
3975
+ for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
3976
+ const pathStart = path[pathIndex];
3977
+ const pathEnd = path[pathIndex + 1];
3978
+ const isVertical5 = Math.abs(pathStart.x - pathEnd.x) < COINCIDENT_EPS;
3979
+ const isHorizontal4 = Math.abs(pathStart.y - pathEnd.y) < COINCIDENT_EPS;
3980
+ if (!isVertical5 && !isHorizontal4) continue;
3981
+ const pathBounds = getStrokeBounds(pathStart, pathEnd, isVertical5);
3982
+ for (const trace of traces) {
3983
+ for (let traceIndex = 0; traceIndex < trace.tracePath.length - 1; traceIndex++) {
3984
+ const traceStart = trace.tracePath[traceIndex];
3985
+ const traceEnd = trace.tracePath[traceIndex + 1];
3986
+ const isParallel = isVertical5 ? Math.abs(traceStart.x - traceEnd.x) < COINCIDENT_EPS : Math.abs(traceStart.y - traceEnd.y) < COINCIDENT_EPS;
3987
+ if (!isParallel) continue;
3988
+ const overlap = boundsIntersection(
3989
+ pathBounds,
3990
+ getStrokeBounds(traceStart, traceEnd, isVertical5)
3991
+ );
3992
+ if (!overlap) continue;
3993
+ const overlapLength = isVertical5 ? overlap.maxY - overlap.minY : overlap.maxX - overlap.minX;
3994
+ if (overlapLength > COINCIDENT_EPS) return true;
3995
+ }
3996
+ }
3997
+ }
3998
+ return false;
3999
+ };
3957
4000
 
3958
4001
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3959
4002
  var MAX_TRIES = 5;
@@ -5307,6 +5350,53 @@ var balanceZShapes = ({
5307
5350
  };
5308
5351
  };
5309
5352
 
5353
+ // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getRailAlignmentFallbackCoordinates.ts
5354
+ import { boundsIntersection as boundsIntersection2 } from "@tscircuit/math-utils";
5355
+ var getAlongBounds = (orientation, minAlong, maxAlong) => orientation === "vertical" ? { minX: 0, maxX: 0, minY: minAlong, maxY: maxAlong } : { minX: minAlong, maxX: maxAlong, minY: 0, maxY: 0 };
5356
+ var hasPositiveAlongOverlap = (orientation, first, second) => {
5357
+ const overlap = boundsIntersection2(first, second);
5358
+ if (!overlap) return false;
5359
+ const overlapLength = orientation === "vertical" ? overlap.maxY - overlap.minY : overlap.maxX - overlap.minX;
5360
+ return overlapLength > RAIL_ALIGNMENT_EPSILON;
5361
+ };
5362
+ var getRailAlignmentFallbackCoordinates = ({
5363
+ group,
5364
+ originalCoordinates,
5365
+ otherNetTraces
5366
+ }) => {
5367
+ const orientation = group[0].orientation;
5368
+ const groupAlongBounds = group.map(
5369
+ (segment) => getAlongBounds(orientation, segment.minAlong, segment.maxAlong)
5370
+ );
5371
+ const fallbackClearance = SCHEMATIC_TRACE_MIN_VISUAL_CENTERLINE_CLEARANCE;
5372
+ const coordinates = [];
5373
+ for (const trace of otherNetTraces) {
5374
+ for (let index = 0; index < trace.tracePath.length - 1; index++) {
5375
+ const start = trace.tracePath[index];
5376
+ const end = trace.tracePath[index + 1];
5377
+ if (getRailOrientation(start, end) !== orientation) continue;
5378
+ const minAlong = orientation === "vertical" ? Math.min(start.y, end.y) : Math.min(start.x, end.x);
5379
+ const maxAlong = orientation === "vertical" ? Math.max(start.y, end.y) : Math.max(start.x, end.x);
5380
+ const traceAlongBounds = getAlongBounds(orientation, minAlong, maxAlong);
5381
+ if (!groupAlongBounds.some(
5382
+ (bounds) => hasPositiveAlongOverlap(orientation, bounds, traceAlongBounds)
5383
+ )) {
5384
+ continue;
5385
+ }
5386
+ const coordinate = orientation === "vertical" ? start.x : start.y;
5387
+ const touchesOriginalCandidate = originalCoordinates.some(
5388
+ (originalCoordinate) => Math.abs(originalCoordinate - coordinate) <= SCHEMATIC_TRACE_MIN_CENTERLINE_CLEARANCE
5389
+ );
5390
+ if (!touchesOriginalCandidate) continue;
5391
+ coordinates.push(
5392
+ coordinate - fallbackClearance,
5393
+ coordinate + fallbackClearance
5394
+ );
5395
+ }
5396
+ }
5397
+ return getDistinctCoordinates(coordinates);
5398
+ };
5399
+
5310
5400
  // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts
5311
5401
  var moveRailSegments = (trace, segments, coordinate) => {
5312
5402
  const pointsToMove = /* @__PURE__ */ new Set();
@@ -5578,7 +5668,7 @@ var evaluateRailGroup = ({
5578
5668
  (trace) => groupTraceIds.has(trace.mspPairId)
5579
5669
  );
5580
5670
  const baseline = getTraceGeometryMetrics(originalGroupTraces, traces);
5581
- const coordinates = getDistinctCoordinates(
5671
+ const originalCoordinates = getDistinctCoordinates(
5582
5672
  group.map((segment) => segment.coordinate)
5583
5673
  );
5584
5674
  const otherNetTraces = traces.filter(
@@ -5587,58 +5677,72 @@ var evaluateRailGroup = ({
5587
5677
  const immutableSameNetTraces = traces.filter(
5588
5678
  (trace) => trace.globalConnNetId === group[0].globalConnNetId && !eligibleTraceIds.has(trace.mspPairId)
5589
5679
  );
5590
- let best = null;
5591
- for (const coordinate of coordinates) {
5592
- const candidateMap = /* @__PURE__ */ new Map();
5593
- for (const trace of originalGroupTraces) {
5594
- const candidateTrace = moveRailSegments(
5595
- trace,
5596
- group.filter((segment) => segment.traceId === trace.mspPairId),
5597
- coordinate
5680
+ const evaluateCoordinates = (coordinates) => {
5681
+ let best = null;
5682
+ for (const coordinate of coordinates) {
5683
+ const candidateMap = /* @__PURE__ */ new Map();
5684
+ for (const trace of originalGroupTraces) {
5685
+ const candidateTrace = moveRailSegments(
5686
+ trace,
5687
+ group.filter((segment) => segment.traceId === trace.mspPairId),
5688
+ coordinate
5689
+ );
5690
+ candidateMap.set(trace.mspPairId, candidateTrace);
5691
+ }
5692
+ const candidateTraces = [...candidateMap.values()];
5693
+ const allCandidateTraces = traces.map(
5694
+ (trace) => candidateMap.get(trace.mspPairId) ?? trace
5598
5695
  );
5599
- candidateMap.set(trace.mspPairId, candidateTrace);
5600
- }
5601
- const candidateTraces = [...candidateMap.values()];
5602
- const allCandidateTraces = traces.map(
5603
- (trace) => candidateMap.get(trace.mspPairId) ?? trace
5604
- );
5605
- const candidatesAreClear = candidateTraces.every(
5606
- (candidate2) => !isPathCollidingWithObstacles(candidate2.tracePath, obstacles) && detectTraceLabelOverlap({
5607
- traces: [candidate2],
5608
- netLabels: netLabelPlacements
5609
- }).length === 0 && !doesPathCoincideWithTraces(candidate2.tracePath, otherNetTraces) && !doesPathCoincideWithTraces(
5610
- candidate2.tracePath,
5611
- immutableSameNetTraces.filter(
5612
- (trace) => trace.mspPairId !== candidate2.mspPairId
5696
+ const candidatesAreClear = candidateTraces.every(
5697
+ (candidate2) => !isPathCollidingWithObstacles(candidate2.tracePath, obstacles) && detectTraceLabelOverlap({
5698
+ traces: [candidate2],
5699
+ netLabels: netLabelPlacements
5700
+ }).length === 0 && !doesPathOverlapTraceStrokes(candidate2.tracePath, otherNetTraces) && !doesPathCoincideWithTraces(
5701
+ candidate2.tracePath,
5702
+ immutableSameNetTraces.filter(
5703
+ (trace) => trace.mspPairId !== candidate2.mspPairId
5704
+ )
5613
5705
  )
5614
- )
5615
- );
5616
- if (!candidatesAreClear) continue;
5617
- if (!preservesLabelAnchors(netLabelPlacements, traces, allCandidateTraces)) {
5618
- continue;
5619
- }
5620
- const metrics = getTraceGeometryMetrics(candidateTraces, allCandidateTraces);
5621
- if (metrics.otherNetCrossings > baseline.otherNetCrossings) continue;
5622
- if (!isReadabilityImprovement(metrics, baseline)) continue;
5623
- const score = {
5624
- ...metrics,
5625
- displacement: group.reduce(
5626
- (sum, segment) => sum + Math.abs(segment.coordinate - coordinate),
5627
- 0
5628
- ),
5629
- coordinate
5630
- };
5631
- const changedTraceIds = candidateTraces.filter((candidate2) => {
5632
- const original = traces.find(
5633
- (trace) => trace.mspPairId === candidate2.mspPairId
5634
5706
  );
5635
- return tracePathChanged(original, candidate2);
5636
- }).map((trace) => trace.mspPairId);
5637
- if (changedTraceIds.length === 0) continue;
5638
- const candidate = { traces: allCandidateTraces, changedTraceIds, score };
5639
- if (!best || scoreIsBetter(candidate.score, best.score)) best = candidate;
5640
- }
5641
- return best;
5707
+ if (!candidatesAreClear) continue;
5708
+ if (!preservesLabelAnchors(netLabelPlacements, traces, allCandidateTraces)) {
5709
+ continue;
5710
+ }
5711
+ const metrics = getTraceGeometryMetrics(
5712
+ candidateTraces,
5713
+ allCandidateTraces
5714
+ );
5715
+ if (metrics.otherNetCrossings > baseline.otherNetCrossings) continue;
5716
+ if (!isReadabilityImprovement(metrics, baseline)) continue;
5717
+ const score = {
5718
+ ...metrics,
5719
+ displacement: group.reduce(
5720
+ (sum, segment) => sum + Math.abs(segment.coordinate - coordinate),
5721
+ 0
5722
+ ),
5723
+ coordinate
5724
+ };
5725
+ const changedTraceIds = candidateTraces.filter((candidate2) => {
5726
+ const original = traces.find(
5727
+ (trace) => trace.mspPairId === candidate2.mspPairId
5728
+ );
5729
+ return tracePathChanged(original, candidate2);
5730
+ }).map((trace) => trace.mspPairId);
5731
+ if (changedTraceIds.length === 0) continue;
5732
+ const candidate = { traces: allCandidateTraces, changedTraceIds, score };
5733
+ if (!best || scoreIsBetter(candidate.score, best.score)) best = candidate;
5734
+ }
5735
+ return best;
5736
+ };
5737
+ const originalCandidate = evaluateCoordinates(originalCoordinates);
5738
+ if (originalCandidate) return originalCandidate;
5739
+ return evaluateCoordinates(
5740
+ getRailAlignmentFallbackCoordinates({
5741
+ group,
5742
+ originalCoordinates,
5743
+ otherNetTraces
5744
+ })
5745
+ );
5642
5746
  };
5643
5747
 
5644
5748
  // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getComponentSideRailSegments.ts
@@ -8948,6 +9052,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
8948
9052
  }
8949
9053
  getOutput() {
8950
9054
  return {
9055
+ traces: this.traces,
8951
9056
  netLabelPlacements: this.outputNetLabelPlacements
8952
9057
  };
8953
9058
  }
@@ -9000,15 +9105,25 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9000
9105
  center,
9001
9106
  width: label.width,
9002
9107
  height: label.height,
9003
- status: this.getCandidateStatus(bounds, labelIndex),
9108
+ status: this.getCandidateStatus(bounds, labelIndex, candidate),
9004
9109
  selected: false
9005
9110
  };
9006
9111
  }
9007
- getCandidateStatus(bounds, labelIndex) {
9112
+ getCandidateStatus(bounds, labelIndex, candidate) {
9008
9113
  if (this.intersectsAnyChip(bounds)) return "chip-collision";
9009
9114
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem))
9010
9115
  return "text-collision";
9011
- if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
9116
+ const candidateTraceMap = candidate.reroutedTracePath ? {
9117
+ ...this.traceMap,
9118
+ [candidate.traceId]: {
9119
+ ...this.traceMap[candidate.traceId],
9120
+ tracePath: candidate.reroutedTracePath
9121
+ }
9122
+ } : this.traceMap;
9123
+ if (traceCrossesBoundsInterior(bounds, candidateTraceMap)) {
9124
+ return "trace-collision";
9125
+ }
9126
+ if (candidate.reroutedTracePath && !this.isReroutedTraceClear(candidate, labelIndex)) {
9012
9127
  return "trace-collision";
9013
9128
  }
9014
9129
  if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
@@ -9017,6 +9132,23 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9017
9132
  return "valid";
9018
9133
  }
9019
9134
  applyCandidate(labelIndex, label, candidate) {
9135
+ if (candidate.reroutedTracePath) {
9136
+ const originalTrace = this.traceMap[candidate.traceId];
9137
+ const reroutedTrace = {
9138
+ ...originalTrace,
9139
+ tracePath: candidate.reroutedTracePath
9140
+ };
9141
+ this.traces = this.traces.map(
9142
+ (trace) => trace.mspPairId === candidate.traceId ? reroutedTrace : trace
9143
+ );
9144
+ this.traceMap[candidate.traceId] = reroutedTrace;
9145
+ this.outputNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
9146
+ trace: originalTrace,
9147
+ originalTracePath: originalTrace.tracePath,
9148
+ reroutedTracePath: candidate.reroutedTracePath,
9149
+ netLabelPlacements: this.outputNetLabelPlacements
9150
+ });
9151
+ }
9020
9152
  this.outputNetLabelPlacements[labelIndex] = {
9021
9153
  ...label,
9022
9154
  anchorPoint: candidate.anchorPoint,
@@ -9042,30 +9174,152 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
9042
9174
  });
9043
9175
  }
9044
9176
  getCornerCandidatesForLabel(label) {
9045
- const isVertical5 = label.orientation === "y+" || label.orientation === "y-";
9046
- const candidates = [];
9047
- const seenCornerKeys = /* @__PURE__ */ new Set();
9048
- for (const trace of this.getTraceLinesForLabel(label)) {
9177
+ const anchorAlignedCandidates = [];
9178
+ const railAlignedCandidates = [];
9179
+ const labelTraces = this.getTraceLinesForLabel(label);
9180
+ const allowRailAlignedFallback = this.isConfiguredRailLabel(label) && !labelTraces.some(
9181
+ (trace) => getTraceCorners(trace.tracePath).some(
9182
+ (corner) => this.pointsEqual(corner, label.anchorPoint)
9183
+ )
9184
+ );
9185
+ for (const trace of labelTraces) {
9049
9186
  const path = trace.tracePath;
9050
9187
  const pins = [path[0], path[path.length - 1]];
9051
9188
  for (const anchorPoint of getTraceCorners(path)) {
9052
- const key = `${anchorPoint.x}:${anchorPoint.y}`;
9053
- if (seenCornerKeys.has(key)) continue;
9054
- seenCornerKeys.add(key);
9055
- const pinAligned = pins.some((pin) => {
9056
- if (isVertical5) return Math.abs(pin.x - anchorPoint.x) < EPS6;
9057
- return Math.abs(pin.y - anchorPoint.y) < EPS6;
9058
- });
9189
+ const cornerIndex = path.indexOf(anchorPoint);
9190
+ const anchorAlignedToPin = pins.some(
9191
+ (pin) => Math.abs(pin.x - anchorPoint.x) <= EPS6
9192
+ );
9193
+ const pinAligned = anchorAlignedToPin || allowRailAlignedFallback && this.isPinAlignedCorner(path, cornerIndex);
9059
9194
  if (!pinAligned) continue;
9060
- candidates.push({
9195
+ const candidates = anchorAlignedToPin ? anchorAlignedCandidates : railAlignedCandidates;
9196
+ candidates.push(
9197
+ {
9198
+ anchorPoint,
9199
+ traceId: trace.mspPairId,
9200
+ distance: getDistance(anchorPoint, label.anchorPoint),
9201
+ pinAligned
9202
+ },
9203
+ ...this.getClearanceShiftedCornerCandidates({
9204
+ label,
9205
+ trace,
9206
+ cornerIndex,
9207
+ pinAligned
9208
+ })
9209
+ );
9210
+ }
9211
+ }
9212
+ return [
9213
+ ...anchorAlignedCandidates.sort((a, b) => a.distance - b.distance),
9214
+ ...railAlignedCandidates.sort((a, b) => a.distance - b.distance)
9215
+ ];
9216
+ }
9217
+ isConfiguredRailLabel(label) {
9218
+ if (!label.netId) return false;
9219
+ return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
9220
+ (orientation) => orientation === "y+" || orientation === "y-"
9221
+ );
9222
+ }
9223
+ pointsEqual(a, b) {
9224
+ return Math.abs(a.x - b.x) <= EPS6 && Math.abs(a.y - b.y) <= EPS6;
9225
+ }
9226
+ getVerticalSegmentPointIndices(path, cornerIndex) {
9227
+ const corner = path[cornerIndex];
9228
+ const previousIsVertical = Math.abs(path[cornerIndex - 1].x - corner.x) <= EPS6;
9229
+ const nextIsVertical = Math.abs(path[cornerIndex + 1].x - corner.x) <= EPS6;
9230
+ if (previousIsVertical === nextIsVertical) return null;
9231
+ const indices = previousIsVertical ? [cornerIndex - 1, cornerIndex] : [cornerIndex, cornerIndex + 1];
9232
+ return indices;
9233
+ }
9234
+ isPinAlignedCorner(path, cornerIndex) {
9235
+ const verticalPointIndices = this.getVerticalSegmentPointIndices(
9236
+ path,
9237
+ cornerIndex
9238
+ );
9239
+ if (!verticalPointIndices) return false;
9240
+ const [startIndex, endIndex] = verticalPointIndices;
9241
+ return startIndex <= 1 || endIndex >= path.length - 2;
9242
+ }
9243
+ getClearanceShiftedCornerCandidates({
9244
+ label,
9245
+ trace,
9246
+ cornerIndex,
9247
+ pinAligned
9248
+ }) {
9249
+ if (label.orientation !== "y+" && label.orientation !== "y-") return [];
9250
+ const path = trace.tracePath;
9251
+ const corner = path[cornerIndex];
9252
+ const verticalPointIndices = this.getVerticalSegmentPointIndices(
9253
+ path,
9254
+ cornerIndex
9255
+ );
9256
+ if (!verticalPointIndices || verticalPointIndices.includes(0) || verticalPointIndices.includes(path.length - 1)) {
9257
+ return [];
9258
+ }
9259
+ const center = getCenterFromAnchor(
9260
+ corner,
9261
+ label.orientation,
9262
+ label.width,
9263
+ label.height
9264
+ );
9265
+ const labelBounds = getRectBounds(center, label.width, label.height);
9266
+ const collidingChipBounds = this.inputProblem.chips.map((chip) => getRectBounds(chip.center, chip.width, chip.height)).filter((chipBounds) => rectsOverlap(labelBounds, chipBounds));
9267
+ const shiftedCoordinates = /* @__PURE__ */ new Set();
9268
+ for (const chipBounds of collidingChipBounds) {
9269
+ shiftedCoordinates.add(chipBounds.minX - label.width / 2);
9270
+ shiftedCoordinates.add(chipBounds.maxX + label.width / 2);
9271
+ }
9272
+ return [...shiftedCoordinates].flatMap((x) => {
9273
+ if (Math.abs(x - corner.x) <= EPS6) return [];
9274
+ const reroutedTracePath = simplifyPath(
9275
+ path.map(
9276
+ (point, pointIndex) => verticalPointIndices.includes(pointIndex) ? { ...point, x } : point
9277
+ )
9278
+ );
9279
+ const anchorPoint = { x, y: corner.y };
9280
+ const remainsCorner = getTraceCorners(reroutedTracePath).some(
9281
+ (point) => this.pointsEqual(point, anchorPoint)
9282
+ );
9283
+ if (!remainsCorner) return [];
9284
+ return [
9285
+ {
9061
9286
  anchorPoint,
9062
9287
  traceId: trace.mspPairId,
9063
9288
  distance: getDistance(anchorPoint, label.anchorPoint),
9064
- pinAligned
9065
- });
9066
- }
9289
+ pinAligned,
9290
+ reroutedTracePath
9291
+ }
9292
+ ];
9293
+ });
9294
+ }
9295
+ isReroutedTraceClear(candidate, labelIndex) {
9296
+ const reroutedTracePath = candidate.reroutedTracePath;
9297
+ if (isPathCollidingWithObstacles(
9298
+ reroutedTracePath,
9299
+ getObstacleRects(this.inputProblem)
9300
+ )) {
9301
+ return false;
9302
+ }
9303
+ const candidateTrace = this.traceMap[candidate.traceId];
9304
+ const otherNetTraces = this.traces.filter(
9305
+ (trace) => trace.mspPairId !== candidate.traceId && trace.globalConnNetId !== candidateTrace.globalConnNetId
9306
+ );
9307
+ if (doesPathOverlapTraceStrokes(reroutedTracePath, otherNetTraces)) {
9308
+ return false;
9067
9309
  }
9068
- return candidates.sort((a, b) => a.distance - b.distance);
9310
+ const reroutedTraceMap = {
9311
+ [candidate.traceId]: {
9312
+ ...candidateTrace,
9313
+ tracePath: reroutedTracePath
9314
+ }
9315
+ };
9316
+ return this.outputNetLabelPlacements.every((label, index) => {
9317
+ if (index === labelIndex) return true;
9318
+ return !traceCrossesBoundsInterior(
9319
+ getRectBounds(label.center, label.width, label.height),
9320
+ reroutedTraceMap
9321
+ );
9322
+ });
9069
9323
  }
9070
9324
  getTraceLinesForLabel(label) {
9071
9325
  return this.traces.filter(
@@ -11380,9 +11634,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11380
11634
  (instance) => [
11381
11635
  {
11382
11636
  inputProblem: instance.inputProblem,
11383
- traces: Object.values(
11384
- instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
11385
- ),
11637
+ traces: instance.railNetLabelCornerPlacementSolver.getOutput().traces,
11386
11638
  netLabelPlacements: instance.railNetLabelCornerPlacementSolver.outputNetLabelPlacements
11387
11639
  }
11388
11640
  ]
@@ -11393,9 +11645,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11393
11645
  (instance) => [
11394
11646
  {
11395
11647
  inputProblem: instance.inputProblem,
11396
- traces: Object.values(
11397
- instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
11398
- ),
11648
+ traces: instance.railNetLabelCornerPlacementSolver.getOutput().traces,
11399
11649
  netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
11400
11650
  }
11401
11651
  ]
@@ -11425,12 +11675,34 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
11425
11675
  "netLabelTraceCollisionSolver",
11426
11676
  NetLabelTraceCollisionSolver,
11427
11677
  (instance) => {
11428
- const previousCollisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
11678
+ const previousOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
11679
+ const alignmentOutput = instance.traceCleanupSolver2.getOutput();
11680
+ const previousTraceMap = new Map(
11681
+ previousOutput.traces.map((trace) => [trace.mspPairId, trace])
11682
+ );
11683
+ let netLabelPlacements = previousOutput.netLabelPlacements;
11684
+ for (const trace of alignmentOutput.traces) {
11685
+ const previousTrace = previousTraceMap.get(trace.mspPairId);
11686
+ if (!previousTrace || previousTrace.tracePath === trace.tracePath) {
11687
+ continue;
11688
+ }
11689
+ const currentLabels = netLabelPlacements;
11690
+ const movedLabels = moveAttachedLabelsToReroutedTrace({
11691
+ trace: previousTrace,
11692
+ originalTracePath: previousTrace.tracePath,
11693
+ reroutedTracePath: trace.tracePath,
11694
+ netLabelPlacements: currentLabels
11695
+ });
11696
+ netLabelPlacements = movedLabels.map((movedLabel, labelIndex) => {
11697
+ const label = currentLabels[labelIndex];
11698
+ return label.orientation === "y+" || label.orientation === "y-" ? movedLabel : label;
11699
+ });
11700
+ }
11429
11701
  return [
11430
11702
  {
11431
11703
  inputProblem: instance.inputProblem,
11432
- traces: instance.traceCleanupSolver2.getOutput().traces,
11433
- netLabelPlacements: previousCollisionOutput.netLabelPlacements
11704
+ traces: alignmentOutput.traces,
11705
+ netLabelPlacements
11434
11706
  }
11435
11707
  ];
11436
11708
  }