@tscircuit/schematic-trace-solver 0.0.93 → 0.0.95

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 (35) hide show
  1. package/dist/index.d.ts +12 -6
  2. package/dist/index.js +71 -16
  3. package/lib/solvers/Example28Solver/reroute.ts +71 -7
  4. package/lib/solvers/{VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts → RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts} +27 -9
  5. package/lib/solvers/{VccNetLabelCornerPlacementSolver → RailNetLabelCornerPlacementSolver}/types.ts +7 -1
  6. package/lib/solvers/{VccNetLabelCornerPlacementSolver → RailNetLabelCornerPlacementSolver}/visualize.ts +1 -1
  7. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +6 -5
  8. package/package.json +1 -1
  9. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +5 -5
  10. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +314 -314
  11. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +4 -4
  12. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +2 -2
  13. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +8 -8
  14. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +4 -4
  15. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
  17. package/tests/examples/__snapshots__/example01.snap.svg +2 -2
  18. package/tests/examples/__snapshots__/example02.snap.svg +4 -4
  19. package/tests/examples/__snapshots__/example07.snap.svg +2 -2
  20. package/tests/examples/__snapshots__/example12.snap.svg +2 -2
  21. package/tests/examples/__snapshots__/example14.snap.svg +36 -36
  22. package/tests/examples/__snapshots__/example15.snap.svg +2 -2
  23. package/tests/examples/__snapshots__/example18.snap.svg +4 -4
  24. package/tests/examples/__snapshots__/example19.snap.svg +22 -22
  25. package/tests/examples/__snapshots__/example21.snap.svg +4 -4
  26. package/tests/examples/__snapshots__/example25.snap.svg +35 -35
  27. package/tests/examples/__snapshots__/example29.snap.svg +10 -10
  28. package/tests/examples/__snapshots__/example31.snap.svg +6 -6
  29. package/tests/examples/__snapshots__/example32.snap.svg +2 -2
  30. package/tests/examples/__snapshots__/example46.snap.svg +2 -2
  31. package/tests/examples/__snapshots__/example47.snap.svg +2 -2
  32. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +4 -4
  33. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +2 -2
  34. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +2 -2
  35. /package/lib/solvers/{VccNetLabelCornerPlacementSolver → RailNetLabelCornerPlacementSolver}/geometry.ts +0 -0
package/dist/index.d.ts CHANGED
@@ -788,7 +788,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
788
788
  visualize(): GraphicsObject;
789
789
  }
790
790
 
791
- interface VccNetLabelCornerPlacementSolverParams {
791
+ interface RailNetLabelCornerPlacementSolverParams {
792
792
  inputProblem: InputProblem;
793
793
  traces: SolvedTracePath[];
794
794
  netLabelPlacements: NetLabelPlacement[];
@@ -797,6 +797,12 @@ type TraceCornerCandidate = {
797
797
  anchorPoint: Point;
798
798
  traceId: string;
799
799
  distance: number;
800
+ /**
801
+ * True when the corner lines up with one of the trace's pins along the
802
+ * label's stub axis (same x for a vertical rail label), so the label reads as
803
+ * a clean stub off that pin rather than floating over a mid-trace bend.
804
+ */
805
+ pinAligned: boolean;
800
806
  };
801
807
  type CornerCandidateStatus = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
802
808
  type EvaluatedCornerCandidate = TraceCornerCandidate & {
@@ -807,7 +813,7 @@ type EvaluatedCornerCandidate = TraceCornerCandidate & {
807
813
  selected: boolean;
808
814
  };
809
815
 
810
- declare class VccNetLabelCornerPlacementSolver extends BaseSolver {
816
+ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
811
817
  inputProblem: InputProblem;
812
818
  traces: SolvedTracePath[];
813
819
  netLabelPlacements: NetLabelPlacement[];
@@ -819,8 +825,8 @@ declare class VccNetLabelCornerPlacementSolver extends BaseSolver {
819
825
  private queuedCornerCandidates;
820
826
  private shouldAdvanceToNextLabel;
821
827
  private traceMap;
822
- constructor(params: VccNetLabelCornerPlacementSolverParams);
823
- getConstructorParams(): ConstructorParameters<typeof VccNetLabelCornerPlacementSolver>[0];
828
+ constructor(params: RailNetLabelCornerPlacementSolverParams);
829
+ getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
824
830
  _step(): void;
825
831
  getOutput(): {
826
832
  netLabelPlacements: NetLabelPlacement[];
@@ -1021,7 +1027,7 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1021
1027
  traceCleanupSolver?: TraceCleanupSolver;
1022
1028
  example28Solver?: Example28Solver;
1023
1029
  availableNetOrientationSolver?: AvailableNetOrientationSolver;
1024
- vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver;
1030
+ railNetLabelCornerPlacementSolver?: RailNetLabelCornerPlacementSolver;
1025
1031
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver;
1026
1032
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
1027
1033
  netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver;
@@ -1030,7 +1036,7 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1030
1036
  timeSpentOnPhase: Record<string, number>;
1031
1037
  firstIterationOfPhase: Record<string, number>;
1032
1038
  inputProblem: InputProblem;
1033
- pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver>)[];
1039
+ pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof RailNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver>)[];
1034
1040
  constructor(inputProblem: InputProblem);
1035
1041
  getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
1036
1042
  currentPipelineStepIndex: number;
package/dist/index.js CHANGED
@@ -5653,6 +5653,30 @@ var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5653
5653
 
5654
5654
  // lib/solvers/Example28Solver/reroute.ts
5655
5655
  var LABEL_CLEARANCE = 0.1;
5656
+ var MAX_CORRIDOR_SHIFTS = 16;
5657
+ var corridorHasOtherNetTrace = ({
5658
+ x,
5659
+ lowY,
5660
+ highY,
5661
+ outputTraces,
5662
+ netId
5663
+ }) => {
5664
+ for (const other of outputTraces) {
5665
+ if (other.globalConnNetId === netId) continue;
5666
+ const path = other.tracePath;
5667
+ for (let i = 0; i + 1 < path.length; i++) {
5668
+ const start = path[i];
5669
+ const end = path[i + 1];
5670
+ if (Math.abs(start.x - end.x) >= 1e-9) continue;
5671
+ if (Math.abs(start.x - x) >= LABEL_CLEARANCE / 2) continue;
5672
+ const segLowY = Math.min(start.y, end.y);
5673
+ const segHighY = Math.max(start.y, end.y);
5674
+ if (Math.min(highY, segHighY) - Math.max(lowY, segLowY) > 1e-6)
5675
+ return true;
5676
+ }
5677
+ }
5678
+ return false;
5679
+ };
5656
5680
  var findBestReroutePath = ({
5657
5681
  trace,
5658
5682
  obstacleLabel,
@@ -5691,8 +5715,11 @@ var generateRerouteCandidateResults = ({
5691
5715
  const seen = /* @__PURE__ */ new Set();
5692
5716
  const candidateResults = [];
5693
5717
  const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
5694
- trace,
5695
- label
5718
+ {
5719
+ trace,
5720
+ label,
5721
+ outputTraces
5722
+ }
5696
5723
  );
5697
5724
  if (horizontalSegmentPushCandidate) {
5698
5725
  candidateResults.push(
@@ -5886,7 +5913,11 @@ var selectBestReroutePath = ({
5886
5913
  }
5887
5914
  return bestPath;
5888
5915
  };
5889
- var generateHorizontalSegmentPushCandidate = (trace, label) => {
5916
+ var generateHorizontalSegmentPushCandidate = ({
5917
+ trace,
5918
+ label,
5919
+ outputTraces
5920
+ }) => {
5890
5921
  const labelDirection = dir(label.orientation);
5891
5922
  if (labelDirection.x === 0) return null;
5892
5923
  const path = trace.tracePath;
@@ -5911,6 +5942,20 @@ var generateHorizontalSegmentPushCandidate = (trace, label) => {
5911
5942
  if (labelDirection.x > 0) {
5912
5943
  segmentPushX = bounds.maxX + LABEL_CLEARANCE;
5913
5944
  }
5945
+ const corridorStep = labelDirection.x > 0 ? LABEL_CLEARANCE : -LABEL_CLEARANCE;
5946
+ const verticalLowY = Math.min(verticalStart.y, verticalEnd.y);
5947
+ const verticalHighY = Math.max(verticalStart.y, verticalEnd.y);
5948
+ for (let shift = 0; shift < MAX_CORRIDOR_SHIFTS; shift++) {
5949
+ const occupied = corridorHasOtherNetTrace({
5950
+ x: segmentPushX,
5951
+ lowY: verticalLowY,
5952
+ highY: verticalHighY,
5953
+ outputTraces,
5954
+ netId: trace.globalConnNetId
5955
+ });
5956
+ if (!occupied) break;
5957
+ segmentPushX += corridorStep;
5958
+ }
5914
5959
  const segmentPushStartY = getClearedHorizontalY({
5915
5960
  start: previousAnchor,
5916
5961
  end: { x: segmentPushX, y: verticalStart.y },
@@ -7257,7 +7302,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7257
7302
  }
7258
7303
  };
7259
7304
 
7260
- // lib/solvers/VccNetLabelCornerPlacementSolver/geometry.ts
7305
+ // lib/solvers/RailNetLabelCornerPlacementSolver/geometry.ts
7261
7306
  var EPS9 = 1e-6;
7262
7307
  var isTraceLine = (trace) => getUniquePinCount(trace.pinIds) >= 2;
7263
7308
  var rectsOverlap2 = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS9 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS9;
@@ -7290,10 +7335,10 @@ var isPointOnSegment = (point, start, end) => {
7290
7335
  };
7291
7336
  var getSegmentOrientation2 = (a, b) => Math.abs(a.y - b.y) <= EPS9 ? "horizontal" : "vertical";
7292
7337
 
7293
- // lib/solvers/VccNetLabelCornerPlacementSolver/visualize.ts
7338
+ // lib/solvers/RailNetLabelCornerPlacementSolver/visualize.ts
7294
7339
  var CANDIDATE_SELECTED_COLOR3 = "blue";
7295
7340
  var CANDIDATE_REJECTED_COLOR3 = "red";
7296
- var visualizeVccNetLabelCornerPlacementSolver = (state) => {
7341
+ var visualizeRailNetLabelCornerPlacementSolver = (state) => {
7297
7342
  const graphics = visualizeInputProblem(state.inputProblem);
7298
7343
  ensureGraphicsArrays2(graphics);
7299
7344
  drawTraces3(graphics, state.traces);
@@ -7374,8 +7419,8 @@ var ensureGraphicsArrays2 = (graphics) => {
7374
7419
  if (!graphics.texts) graphics.texts = [];
7375
7420
  };
7376
7421
 
7377
- // lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts
7378
- var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7422
+ // lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts
7423
+ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
7379
7424
  inputProblem;
7380
7425
  traces;
7381
7426
  netLabelPlacements;
@@ -7507,7 +7552,8 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7507
7552
  };
7508
7553
  }
7509
7554
  shouldProcessLabel(label) {
7510
- return label.netId === "VCC" && this.getCornerCandidatesForLabel(label).length > 0;
7555
+ const isVerticalRailLabel = label.orientation === "y+" || label.orientation === "y-";
7556
+ return isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0;
7511
7557
  }
7512
7558
  intersectsAnyChip(bounds) {
7513
7559
  return this.inputProblem.chips.some(
@@ -7524,17 +7570,26 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7524
7570
  });
7525
7571
  }
7526
7572
  getCornerCandidatesForLabel(label) {
7573
+ const isVertical4 = label.orientation === "y+" || label.orientation === "y-";
7527
7574
  const candidates = [];
7528
7575
  const seenCornerKeys = /* @__PURE__ */ new Set();
7529
7576
  for (const trace of this.getTraceLinesForLabel(label)) {
7530
- for (const anchorPoint of getTraceCorners(trace.tracePath)) {
7577
+ const path = trace.tracePath;
7578
+ const pins = [path[0], path[path.length - 1]];
7579
+ for (const anchorPoint of getTraceCorners(path)) {
7531
7580
  const key = `${anchorPoint.x}:${anchorPoint.y}`;
7532
7581
  if (seenCornerKeys.has(key)) continue;
7533
7582
  seenCornerKeys.add(key);
7583
+ const pinAligned = pins.some((pin) => {
7584
+ if (isVertical4) return Math.abs(pin.x - anchorPoint.x) < EPS9;
7585
+ return Math.abs(pin.y - anchorPoint.y) < EPS9;
7586
+ });
7587
+ if (!pinAligned) continue;
7534
7588
  candidates.push({
7535
7589
  anchorPoint,
7536
7590
  traceId: trace.mspPairId,
7537
- distance: getDistance2(anchorPoint, label.anchorPoint)
7591
+ distance: getDistance2(anchorPoint, label.anchorPoint),
7592
+ pinAligned
7538
7593
  });
7539
7594
  }
7540
7595
  }
@@ -7556,7 +7611,7 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7556
7611
  return trace.globalConnNetId === label.globalConnNetId;
7557
7612
  }
7558
7613
  visualize() {
7559
- return visualizeVccNetLabelCornerPlacementSolver({
7614
+ return visualizeRailNetLabelCornerPlacementSolver({
7560
7615
  inputProblem: this.inputProblem,
7561
7616
  traces: this.traces,
7562
7617
  outputNetLabelPlacements: this.outputNetLabelPlacements,
@@ -8970,7 +9025,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
8970
9025
  traceCleanupSolver;
8971
9026
  example28Solver;
8972
9027
  availableNetOrientationSolver;
8973
- vccNetLabelCornerPlacementSolver;
9028
+ railNetLabelCornerPlacementSolver;
8974
9029
  traceAnchoredNetLabelOverlapSolver;
8975
9030
  netLabelTraceCollisionSolver;
8976
9031
  netLabelNetLabelCollisionSolver;
@@ -9132,8 +9187,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9132
9187
  ]
9133
9188
  ),
9134
9189
  definePipelineStep(
9135
- "vccNetLabelCornerPlacementSolver",
9136
- VccNetLabelCornerPlacementSolver,
9190
+ "railNetLabelCornerPlacementSolver",
9191
+ RailNetLabelCornerPlacementSolver,
9137
9192
  (instance) => {
9138
9193
  return [
9139
9194
  {
@@ -9151,7 +9206,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9151
9206
  {
9152
9207
  inputProblem: instance.inputProblem,
9153
9208
  traces: instance.availableNetOrientationSolver.traces,
9154
- netLabelPlacements: instance.vccNetLabelCornerPlacementSolver.outputNetLabelPlacements
9209
+ netLabelPlacements: instance.railNetLabelCornerPlacementSolver.outputNetLabelPlacements
9155
9210
  }
9156
9211
  ]
9157
9212
  ),
@@ -8,13 +8,13 @@ import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceS
8
8
  import { generateRerouteCandidates } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace"
9
9
  import type { InputProblem } from "lib/types/InputProblem"
10
10
  import { dir } from "lib/utils/dir"
11
+ import { getChipBoundaryOverlap } from "./doesPathRunAlongChipBoundary"
11
12
  import {
12
13
  countPathIntersections,
13
14
  getPathKey,
14
15
  getPathLength,
15
16
  isPathCollidingWithChipInterior,
16
17
  } from "./geometry"
17
- import { getChipBoundaryOverlap } from "./doesPathRunAlongChipBoundary"
18
18
  import type {
19
19
  ChipObstacle,
20
20
  RerouteCandidateResult,
@@ -22,6 +22,43 @@ import type {
22
22
  } from "./types"
23
23
 
24
24
  const LABEL_CLEARANCE = 0.1
25
+ // How many LABEL_CLEARANCE-wide corridors to try before giving up on finding a
26
+ // free one for a pushed segment.
27
+ const MAX_CORRIDOR_SHIFTS = 16
28
+
29
+ /**
30
+ * Whether a vertical trace segment of a different net already runs along the
31
+ * corridor at `x`, overlapping the [lowY, highY] span (within a corridor width).
32
+ */
33
+ const corridorHasOtherNetTrace = ({
34
+ x,
35
+ lowY,
36
+ highY,
37
+ outputTraces,
38
+ netId,
39
+ }: {
40
+ x: number
41
+ lowY: number
42
+ highY: number
43
+ outputTraces: SolvedTracePath[]
44
+ netId: string
45
+ }): boolean => {
46
+ for (const other of outputTraces) {
47
+ if (other.globalConnNetId === netId) continue
48
+ const path = other.tracePath
49
+ for (let i = 0; i + 1 < path.length; i++) {
50
+ const start = path[i]!
51
+ const end = path[i + 1]!
52
+ if (Math.abs(start.x - end.x) >= 1e-9) continue
53
+ if (Math.abs(start.x - x) >= LABEL_CLEARANCE / 2) continue
54
+ const segLowY = Math.min(start.y, end.y)
55
+ const segHighY = Math.max(start.y, end.y)
56
+ if (Math.min(highY, segHighY) - Math.max(lowY, segLowY) > 1e-6)
57
+ return true
58
+ }
59
+ }
60
+ return false
61
+ }
25
62
 
26
63
  export const findBestReroutePath = ({
27
64
  trace,
@@ -78,8 +115,11 @@ export const generateRerouteCandidateResults = ({
78
115
  const seen = new Set<string>()
79
116
  const candidateResults: RerouteCandidateResult[] = []
80
117
  const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
81
- trace,
82
- label,
118
+ {
119
+ trace,
120
+ label,
121
+ outputTraces,
122
+ },
83
123
  )
84
124
 
85
125
  if (horizontalSegmentPushCandidate) {
@@ -328,10 +368,15 @@ const selectBestReroutePath = ({
328
368
  return bestPath
329
369
  }
330
370
 
331
- const generateHorizontalSegmentPushCandidate = (
332
- trace: SolvedTracePath,
333
- label: NetLabelPlacement,
334
- ): Point[] | null => {
371
+ const generateHorizontalSegmentPushCandidate = ({
372
+ trace,
373
+ label,
374
+ outputTraces,
375
+ }: {
376
+ trace: SolvedTracePath
377
+ label: NetLabelPlacement
378
+ outputTraces: SolvedTracePath[]
379
+ }): Point[] | null => {
335
380
  const labelDirection = dir(label.orientation)
336
381
  if (labelDirection.x === 0) return null
337
382
 
@@ -367,6 +412,25 @@ const generateHorizontalSegmentPushCandidate = (
367
412
  if (labelDirection.x > 0) {
368
413
  segmentPushX = bounds.maxX + LABEL_CLEARANCE
369
414
  }
415
+
416
+ // Two traces escaping the same label block would otherwise be pushed to the
417
+ // identical corridor and stack on one line. Slide the corridor further out
418
+ // until it no longer coincides with a different-net trace already routed
419
+ // there, giving each escaping trace its own parallel corridor.
420
+ const corridorStep = labelDirection.x > 0 ? LABEL_CLEARANCE : -LABEL_CLEARANCE
421
+ const verticalLowY = Math.min(verticalStart.y, verticalEnd.y)
422
+ const verticalHighY = Math.max(verticalStart.y, verticalEnd.y)
423
+ for (let shift = 0; shift < MAX_CORRIDOR_SHIFTS; shift++) {
424
+ const occupied = corridorHasOtherNetTrace({
425
+ x: segmentPushX,
426
+ lowY: verticalLowY,
427
+ highY: verticalHighY,
428
+ outputTraces,
429
+ netId: trace.globalConnNetId,
430
+ })
431
+ if (!occupied) break
432
+ segmentPushX += corridorStep
433
+ }
370
434
  const segmentPushStartY = getClearedHorizontalY({
371
435
  start: previousAnchor,
372
436
  end: { x: segmentPushX, y: verticalStart.y },
@@ -9,6 +9,7 @@ import {
9
9
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
10
10
  import type { InputProblem } from "lib/types/InputProblem"
11
11
  import {
12
+ EPS,
12
13
  getDistance,
13
14
  getTraceCorners,
14
15
  isTraceLine,
@@ -20,12 +21,12 @@ import type {
20
21
  CornerCandidateStatus,
21
22
  EvaluatedCornerCandidate,
22
23
  TraceCornerCandidate,
23
- VccNetLabelCornerPlacementSolverParams,
24
+ RailNetLabelCornerPlacementSolverParams,
24
25
  } from "./types"
25
- import { visualizeVccNetLabelCornerPlacementSolver } from "./visualize"
26
+ import { visualizeRailNetLabelCornerPlacementSolver } from "./visualize"
26
27
  import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
27
28
 
28
- export class VccNetLabelCornerPlacementSolver extends BaseSolver {
29
+ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
29
30
  inputProblem: InputProblem
30
31
  traces: SolvedTracePath[]
31
32
  netLabelPlacements: NetLabelPlacement[]
@@ -40,7 +41,7 @@ export class VccNetLabelCornerPlacementSolver extends BaseSolver {
40
41
  private shouldAdvanceToNextLabel = false
41
42
  private traceMap: Record<string, SolvedTracePath>
42
43
 
43
- constructor(params: VccNetLabelCornerPlacementSolverParams) {
44
+ constructor(params: RailNetLabelCornerPlacementSolverParams) {
44
45
  super()
45
46
  this.inputProblem = params.inputProblem
46
47
  this.traces = params.traces
@@ -54,7 +55,7 @@ export class VccNetLabelCornerPlacementSolver extends BaseSolver {
54
55
  }
55
56
 
56
57
  override getConstructorParams(): ConstructorParameters<
57
- typeof VccNetLabelCornerPlacementSolver
58
+ typeof RailNetLabelCornerPlacementSolver
58
59
  >[0] {
59
60
  return {
60
61
  inputProblem: this.inputProblem,
@@ -197,9 +198,13 @@ export class VccNetLabelCornerPlacementSolver extends BaseSolver {
197
198
  }
198
199
 
199
200
  private shouldProcessLabel(label: NetLabelPlacement) {
201
+ // Power/ground rail labels (VCC, GND, V3_3, ...) have a fixed vertical
202
+ // orientation and read best snapped to a trace corner rather than floating
203
+ // mid-segment. Signal labels (x+/x-) are left where they are.
204
+ const isVerticalRailLabel =
205
+ label.orientation === "y+" || label.orientation === "y-"
200
206
  return (
201
- label.netId === "VCC" &&
202
- this.getCornerCandidatesForLabel(label).length > 0
207
+ isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0
203
208
  )
204
209
  }
205
210
 
@@ -220,18 +225,31 @@ export class VccNetLabelCornerPlacementSolver extends BaseSolver {
220
225
  }
221
226
 
222
227
  private getCornerCandidatesForLabel(label: NetLabelPlacement) {
228
+ const isVertical = label.orientation === "y+" || label.orientation === "y-"
223
229
  const candidates: TraceCornerCandidate[] = []
224
230
  const seenCornerKeys = new Set<string>()
225
231
 
226
232
  for (const trace of this.getTraceLinesForLabel(label)) {
227
- for (const anchorPoint of getTraceCorners(trace.tracePath)) {
233
+ const path = trace.tracePath
234
+ const pins = [path[0]!, path[path.length - 1]!]
235
+ for (const anchorPoint of getTraceCorners(path)) {
228
236
  const key = `${anchorPoint.x}:${anchorPoint.y}`
229
237
  if (seenCornerKeys.has(key)) continue
230
238
  seenCornerKeys.add(key)
239
+ const pinAligned = pins.some((pin) => {
240
+ if (isVertical) return Math.abs(pin.x - anchorPoint.x) < EPS
241
+ return Math.abs(pin.y - anchorPoint.y) < EPS
242
+ })
243
+ // Only pin-aligned corners are worth snapping a rail label to (a clean
244
+ // stub off a pin). Non-aligned corners are no better than where the
245
+ // label already is, so they aren't offered as candidates — this keeps
246
+ // already-well-placed labels untouched.
247
+ if (!pinAligned) continue
231
248
  candidates.push({
232
249
  anchorPoint,
233
250
  traceId: trace.mspPairId,
234
251
  distance: getDistance(anchorPoint, label.anchorPoint),
252
+ pinAligned,
235
253
  })
236
254
  }
237
255
  }
@@ -259,7 +277,7 @@ export class VccNetLabelCornerPlacementSolver extends BaseSolver {
259
277
  }
260
278
 
261
279
  override visualize(): GraphicsObject {
262
- return visualizeVccNetLabelCornerPlacementSolver({
280
+ return visualizeRailNetLabelCornerPlacementSolver({
263
281
  inputProblem: this.inputProblem,
264
282
  traces: this.traces,
265
283
  outputNetLabelPlacements: this.outputNetLabelPlacements,
@@ -3,7 +3,7 @@ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetL
3
3
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
4
  import type { InputProblem } from "lib/types/InputProblem"
5
5
 
6
- export interface VccNetLabelCornerPlacementSolverParams {
6
+ export interface RailNetLabelCornerPlacementSolverParams {
7
7
  inputProblem: InputProblem
8
8
  traces: SolvedTracePath[]
9
9
  netLabelPlacements: NetLabelPlacement[]
@@ -20,6 +20,12 @@ export type TraceCornerCandidate = {
20
20
  anchorPoint: Point
21
21
  traceId: string
22
22
  distance: number
23
+ /**
24
+ * True when the corner lines up with one of the trace's pins along the
25
+ * label's stub axis (same x for a vertical rail label), so the label reads as
26
+ * a clean stub off that pin rather than floating over a mid-trace bend.
27
+ */
28
+ pinAligned: boolean
23
29
  }
24
30
 
25
31
  export type CornerCandidateStatus =
@@ -9,7 +9,7 @@ import type { EvaluatedCornerCandidate } from "./types"
9
9
  const CANDIDATE_SELECTED_COLOR = "blue"
10
10
  const CANDIDATE_REJECTED_COLOR = "red"
11
11
 
12
- export const visualizeVccNetLabelCornerPlacementSolver = (state: {
12
+ export const visualizeRailNetLabelCornerPlacementSolver = (state: {
13
13
  inputProblem: InputProblem
14
14
  traces: SolvedTracePath[]
15
15
  outputNetLabelPlacements: NetLabelPlacement[]
@@ -23,7 +23,7 @@ import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolve
23
23
  import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
24
24
  import { Example28Solver } from "../Example28Solver/Example28Solver"
25
25
  import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
26
- import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
26
+ import { RailNetLabelCornerPlacementSolver } from "../RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver"
27
27
  import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
28
28
  import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver"
29
29
  import { NetLabelNetLabelCollisionSolver } from "../NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver"
@@ -78,7 +78,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
78
78
  traceCleanupSolver?: TraceCleanupSolver
79
79
  example28Solver?: Example28Solver
80
80
  availableNetOrientationSolver?: AvailableNetOrientationSolver
81
- vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
81
+ railNetLabelCornerPlacementSolver?: RailNetLabelCornerPlacementSolver
82
82
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
83
83
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
84
84
  netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver
@@ -264,8 +264,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
264
264
  ],
265
265
  ),
266
266
  definePipelineStep(
267
- "vccNetLabelCornerPlacementSolver",
268
- VccNetLabelCornerPlacementSolver,
267
+ "railNetLabelCornerPlacementSolver",
268
+ RailNetLabelCornerPlacementSolver,
269
269
  (instance) => {
270
270
  return [
271
271
  {
@@ -285,7 +285,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
285
285
  inputProblem: instance.inputProblem,
286
286
  traces: instance.availableNetOrientationSolver!.traces,
287
287
  netLabelPlacements:
288
- instance.vccNetLabelCornerPlacementSolver!.outputNetLabelPlacements,
288
+ instance.railNetLabelCornerPlacementSolver!
289
+ .outputNetLabelPlacements,
289
290
  },
290
291
  ],
291
292
  ),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.93",
4
+ "version": "0.0.95",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",