@tscircuit/schematic-trace-solver 0.0.94 → 0.0.96

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 (38) hide show
  1. package/dist/index.d.ts +19 -12
  2. package/dist/index.js +78 -57
  3. package/lib/solvers/{VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts → RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts} +27 -9
  4. package/lib/solvers/{VccNetLabelCornerPlacementSolver → RailNetLabelCornerPlacementSolver}/types.ts +7 -1
  5. package/lib/solvers/{VccNetLabelCornerPlacementSolver → RailNetLabelCornerPlacementSolver}/visualize.ts +1 -1
  6. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +17 -12
  7. package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +47 -40
  8. package/lib/types/InputProblem.ts +1 -0
  9. package/package.json +1 -1
  10. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +1 -1
  11. package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +3 -1
  12. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +314 -314
  13. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +5 -5
  14. package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +3 -1
  15. package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +8 -8
  17. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +4 -4
  18. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  19. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
  20. package/tests/examples/__snapshots__/example01.snap.svg +2 -2
  21. package/tests/examples/__snapshots__/example02.snap.svg +4 -4
  22. package/tests/examples/__snapshots__/example07.snap.svg +2 -2
  23. package/tests/examples/__snapshots__/example12.snap.svg +2 -2
  24. package/tests/examples/__snapshots__/example14.snap.svg +36 -36
  25. package/tests/examples/__snapshots__/example15.snap.svg +2 -2
  26. package/tests/examples/__snapshots__/example18.snap.svg +4 -4
  27. package/tests/examples/__snapshots__/example19.snap.svg +22 -22
  28. package/tests/examples/__snapshots__/example21.snap.svg +4 -4
  29. package/tests/examples/__snapshots__/example25.snap.svg +35 -35
  30. package/tests/examples/__snapshots__/example29.snap.svg +10 -10
  31. package/tests/examples/__snapshots__/example31.snap.svg +6 -6
  32. package/tests/examples/__snapshots__/example32.snap.svg +2 -2
  33. package/tests/examples/__snapshots__/example46.snap.svg +2 -2
  34. package/tests/examples/__snapshots__/example47.snap.svg +2 -2
  35. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +4 -4
  36. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +2 -2
  37. package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +2 -2
  38. /package/lib/solvers/{VccNetLabelCornerPlacementSolver → RailNetLabelCornerPlacementSolver}/geometry.ts +0 -0
package/dist/index.d.ts CHANGED
@@ -103,6 +103,7 @@ interface InputProblem {
103
103
  availableNetLabelOrientations: Record<NetId, FacingDirection[]>;
104
104
  maxMspPairDistance?: number;
105
105
  _chipObstacleSpatialIndex?: ChipObstacleSpatialIndex;
106
+ _hideRatsNet?: boolean;
106
107
  }
107
108
 
108
109
  type MspConnectionPairId = string;
@@ -788,7 +789,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
788
789
  visualize(): GraphicsObject;
789
790
  }
790
791
 
791
- interface VccNetLabelCornerPlacementSolverParams {
792
+ interface RailNetLabelCornerPlacementSolverParams {
792
793
  inputProblem: InputProblem;
793
794
  traces: SolvedTracePath[];
794
795
  netLabelPlacements: NetLabelPlacement[];
@@ -797,6 +798,12 @@ type TraceCornerCandidate = {
797
798
  anchorPoint: Point;
798
799
  traceId: string;
799
800
  distance: number;
801
+ /**
802
+ * True when the corner lines up with one of the trace's pins along the
803
+ * label's stub axis (same x for a vertical rail label), so the label reads as
804
+ * a clean stub off that pin rather than floating over a mid-trace bend.
805
+ */
806
+ pinAligned: boolean;
800
807
  };
801
808
  type CornerCandidateStatus = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "netlabel-collision";
802
809
  type EvaluatedCornerCandidate = TraceCornerCandidate & {
@@ -807,7 +814,7 @@ type EvaluatedCornerCandidate = TraceCornerCandidate & {
807
814
  selected: boolean;
808
815
  };
809
816
 
810
- declare class VccNetLabelCornerPlacementSolver extends BaseSolver {
817
+ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
811
818
  inputProblem: InputProblem;
812
819
  traces: SolvedTracePath[];
813
820
  netLabelPlacements: NetLabelPlacement[];
@@ -819,8 +826,8 @@ declare class VccNetLabelCornerPlacementSolver extends BaseSolver {
819
826
  private queuedCornerCandidates;
820
827
  private shouldAdvanceToNextLabel;
821
828
  private traceMap;
822
- constructor(params: VccNetLabelCornerPlacementSolverParams);
823
- getConstructorParams(): ConstructorParameters<typeof VccNetLabelCornerPlacementSolver>[0];
829
+ constructor(params: RailNetLabelCornerPlacementSolverParams);
830
+ getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
824
831
  _step(): void;
825
832
  getOutput(): {
826
833
  netLabelPlacements: NetLabelPlacement[];
@@ -1006,9 +1013,8 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
1006
1013
  onSolved?: (instance: SchematicTracePipelineSolver) => void;
1007
1014
  shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean;
1008
1015
  };
1009
- interface SchematicTracePipelineSolverParams {
1010
- inputProblem: InputProblem;
1011
- allowLongDistanceTraces?: boolean;
1016
+ interface Options {
1017
+ hideRatsNet?: boolean;
1012
1018
  }
1013
1019
  declare class SchematicTracePipelineSolver extends BaseSolver {
1014
1020
  mspConnectionPairSolver?: MspConnectionPairSolver;
@@ -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,9 +1036,10 @@ 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>)[];
1034
- constructor(inputProblem: InputProblem);
1035
- getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
1039
+ hideRatsNet: boolean;
1040
+ 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>)[];
1041
+ constructor(inputProblem: InputProblem, opts?: Options);
1042
+ getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>;
1036
1043
  currentPipelineStepIndex: number;
1037
1044
  private cloneAndCorrectInputProblem;
1038
1045
  _step(): void;
@@ -1046,4 +1053,4 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
1046
1053
  preview(): GraphicsObject;
1047
1054
  }
1048
1055
 
1049
- export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, type SchematicTracePipelineSolverParams, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes };
1056
+ export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, SchematicTraceSingleLineSolver2, type SectionId, type TextBoxes };
package/dist/index.js CHANGED
@@ -327,7 +327,11 @@ var arePinsInDifferentSchematicSections = (inputProblem, p1, p2) => {
327
327
 
328
328
  // lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
329
329
  var visualizeInputProblem = (inputProblem, opts = {}) => {
330
- const { connectionAlpha = 0.8, chipAlpha = 0.8 } = opts;
330
+ const {
331
+ connectionAlpha = 0.8,
332
+ chipAlpha = 0.8,
333
+ hideRatsNet = inputProblem._hideRatsNet ?? false
334
+ } = opts;
331
335
  const graphics = {
332
336
  lines: [],
333
337
  points: [],
@@ -367,47 +371,49 @@ ${pin._facingDirection ?? getPinDirection(pin, chip)}`,
367
371
  strokeColor: "rgba(160, 0, 220, 0.9)"
368
372
  });
369
373
  }
370
- for (const directConn of inputProblem.directConnections) {
371
- const [pinId1, pinId2] = directConn.pinIds;
372
- const pin1 = pinIdMap.get(pinId1);
373
- const pin2 = pinIdMap.get(pinId2);
374
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
375
- continue;
374
+ if (!hideRatsNet) {
375
+ for (const directConn of inputProblem.directConnections) {
376
+ const [pinId1, pinId2] = directConn.pinIds;
377
+ const pin1 = pinIdMap.get(pinId1);
378
+ const pin2 = pinIdMap.get(pinId2);
379
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
380
+ continue;
381
+ }
382
+ graphics.lines.push({
383
+ points: [
384
+ {
385
+ x: pin1.x,
386
+ y: pin1.y
387
+ },
388
+ {
389
+ x: pin2.x,
390
+ y: pin2.y
391
+ }
392
+ ],
393
+ strokeColor: getColorFromString(
394
+ directConn.netId ?? `${pinId1}-${pinId2}`,
395
+ connectionAlpha
396
+ )
397
+ });
376
398
  }
377
- graphics.lines.push({
378
- points: [
379
- {
380
- x: pin1.x,
381
- y: pin1.y
382
- },
383
- {
384
- x: pin2.x,
385
- y: pin2.y
386
- }
387
- ],
388
- strokeColor: getColorFromString(
389
- directConn.netId ?? `${pinId1}-${pinId2}`,
390
- connectionAlpha
391
- )
392
- });
393
- }
394
- for (const netConn of inputProblem.netConnections) {
395
- const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
396
- for (let i = 0; i < pins.length - 1; i++) {
397
- for (let j = i + 1; j < pins.length; j++) {
398
- const pin1 = pins[i];
399
- const pin2 = pins[j];
400
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
401
- continue;
399
+ for (const netConn of inputProblem.netConnections) {
400
+ const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
401
+ for (let i = 0; i < pins.length - 1; i++) {
402
+ for (let j = i + 1; j < pins.length; j++) {
403
+ const pin1 = pins[i];
404
+ const pin2 = pins[j];
405
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
406
+ continue;
407
+ }
408
+ graphics.lines.push({
409
+ points: [
410
+ { x: pin1.x, y: pin1.y },
411
+ { x: pin2.x, y: pin2.y }
412
+ ],
413
+ strokeColor: getColorFromString(netConn.netId, connectionAlpha),
414
+ strokeDash: "4 2"
415
+ });
402
416
  }
403
- graphics.lines.push({
404
- points: [
405
- { x: pin1.x, y: pin1.y },
406
- { x: pin2.x, y: pin2.y }
407
- ],
408
- strokeColor: getColorFromString(netConn.netId, connectionAlpha),
409
- strokeDash: "4 2"
410
- });
411
417
  }
412
418
  }
413
419
  }
@@ -7302,7 +7308,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7302
7308
  }
7303
7309
  };
7304
7310
 
7305
- // lib/solvers/VccNetLabelCornerPlacementSolver/geometry.ts
7311
+ // lib/solvers/RailNetLabelCornerPlacementSolver/geometry.ts
7306
7312
  var EPS9 = 1e-6;
7307
7313
  var isTraceLine = (trace) => getUniquePinCount(trace.pinIds) >= 2;
7308
7314
  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;
@@ -7335,10 +7341,10 @@ var isPointOnSegment = (point, start, end) => {
7335
7341
  };
7336
7342
  var getSegmentOrientation2 = (a, b) => Math.abs(a.y - b.y) <= EPS9 ? "horizontal" : "vertical";
7337
7343
 
7338
- // lib/solvers/VccNetLabelCornerPlacementSolver/visualize.ts
7344
+ // lib/solvers/RailNetLabelCornerPlacementSolver/visualize.ts
7339
7345
  var CANDIDATE_SELECTED_COLOR3 = "blue";
7340
7346
  var CANDIDATE_REJECTED_COLOR3 = "red";
7341
- var visualizeVccNetLabelCornerPlacementSolver = (state) => {
7347
+ var visualizeRailNetLabelCornerPlacementSolver = (state) => {
7342
7348
  const graphics = visualizeInputProblem(state.inputProblem);
7343
7349
  ensureGraphicsArrays2(graphics);
7344
7350
  drawTraces3(graphics, state.traces);
@@ -7419,8 +7425,8 @@ var ensureGraphicsArrays2 = (graphics) => {
7419
7425
  if (!graphics.texts) graphics.texts = [];
7420
7426
  };
7421
7427
 
7422
- // lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts
7423
- var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7428
+ // lib/solvers/RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver.ts
7429
+ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
7424
7430
  inputProblem;
7425
7431
  traces;
7426
7432
  netLabelPlacements;
@@ -7552,7 +7558,8 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7552
7558
  };
7553
7559
  }
7554
7560
  shouldProcessLabel(label) {
7555
- return label.netId === "VCC" && this.getCornerCandidatesForLabel(label).length > 0;
7561
+ const isVerticalRailLabel = label.orientation === "y+" || label.orientation === "y-";
7562
+ return isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0;
7556
7563
  }
7557
7564
  intersectsAnyChip(bounds) {
7558
7565
  return this.inputProblem.chips.some(
@@ -7569,17 +7576,26 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7569
7576
  });
7570
7577
  }
7571
7578
  getCornerCandidatesForLabel(label) {
7579
+ const isVertical4 = label.orientation === "y+" || label.orientation === "y-";
7572
7580
  const candidates = [];
7573
7581
  const seenCornerKeys = /* @__PURE__ */ new Set();
7574
7582
  for (const trace of this.getTraceLinesForLabel(label)) {
7575
- for (const anchorPoint of getTraceCorners(trace.tracePath)) {
7583
+ const path = trace.tracePath;
7584
+ const pins = [path[0], path[path.length - 1]];
7585
+ for (const anchorPoint of getTraceCorners(path)) {
7576
7586
  const key = `${anchorPoint.x}:${anchorPoint.y}`;
7577
7587
  if (seenCornerKeys.has(key)) continue;
7578
7588
  seenCornerKeys.add(key);
7589
+ const pinAligned = pins.some((pin) => {
7590
+ if (isVertical4) return Math.abs(pin.x - anchorPoint.x) < EPS9;
7591
+ return Math.abs(pin.y - anchorPoint.y) < EPS9;
7592
+ });
7593
+ if (!pinAligned) continue;
7579
7594
  candidates.push({
7580
7595
  anchorPoint,
7581
7596
  traceId: trace.mspPairId,
7582
- distance: getDistance2(anchorPoint, label.anchorPoint)
7597
+ distance: getDistance2(anchorPoint, label.anchorPoint),
7598
+ pinAligned
7583
7599
  });
7584
7600
  }
7585
7601
  }
@@ -7601,7 +7617,7 @@ var VccNetLabelCornerPlacementSolver = class extends BaseSolver {
7601
7617
  return trace.globalConnNetId === label.globalConnNetId;
7602
7618
  }
7603
7619
  visualize() {
7604
- return visualizeVccNetLabelCornerPlacementSolver({
7620
+ return visualizeRailNetLabelCornerPlacementSolver({
7605
7621
  inputProblem: this.inputProblem,
7606
7622
  traces: this.traces,
7607
7623
  outputNetLabelPlacements: this.outputNetLabelPlacements,
@@ -9015,7 +9031,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9015
9031
  traceCleanupSolver;
9016
9032
  example28Solver;
9017
9033
  availableNetOrientationSolver;
9018
- vccNetLabelCornerPlacementSolver;
9034
+ railNetLabelCornerPlacementSolver;
9019
9035
  traceAnchoredNetLabelOverlapSolver;
9020
9036
  netLabelTraceCollisionSolver;
9021
9037
  netLabelNetLabelCollisionSolver;
@@ -9024,6 +9040,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9024
9040
  timeSpentOnPhase;
9025
9041
  firstIterationOfPhase;
9026
9042
  inputProblem;
9043
+ hideRatsNet;
9027
9044
  pipelineDef = [
9028
9045
  definePipelineStep(
9029
9046
  "mspConnectionPairSolver",
@@ -9177,8 +9194,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9177
9194
  ]
9178
9195
  ),
9179
9196
  definePipelineStep(
9180
- "vccNetLabelCornerPlacementSolver",
9181
- VccNetLabelCornerPlacementSolver,
9197
+ "railNetLabelCornerPlacementSolver",
9198
+ RailNetLabelCornerPlacementSolver,
9182
9199
  (instance) => {
9183
9200
  return [
9184
9201
  {
@@ -9196,7 +9213,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9196
9213
  {
9197
9214
  inputProblem: instance.inputProblem,
9198
9215
  traces: instance.availableNetOrientationSolver.traces,
9199
- netLabelPlacements: instance.vccNetLabelCornerPlacementSolver.outputNetLabelPlacements
9216
+ netLabelPlacements: instance.railNetLabelCornerPlacementSolver.outputNetLabelPlacements
9200
9217
  }
9201
9218
  ]
9202
9219
  ),
@@ -9223,8 +9240,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9223
9240
  ]
9224
9241
  )
9225
9242
  ];
9226
- constructor(inputProblem) {
9243
+ constructor(inputProblem, opts) {
9227
9244
  super();
9245
+ this.hideRatsNet = opts?.hideRatsNet ?? false;
9228
9246
  this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem);
9229
9247
  this.MAX_ITERATIONS = 1e6;
9230
9248
  this.startTimeOfPhase = {};
@@ -9233,13 +9251,14 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9233
9251
  this.firstIterationOfPhase = {};
9234
9252
  }
9235
9253
  getConstructorParams() {
9236
- return this.inputProblem;
9254
+ return [this.inputProblem, { hideRatsNet: this.hideRatsNet }];
9237
9255
  }
9238
9256
  currentPipelineStepIndex = 0;
9239
9257
  cloneAndCorrectInputProblem(original) {
9240
9258
  const cloned = structuredClone({
9241
9259
  ...original,
9242
- _chipObstacleSpatialIndex: void 0
9260
+ _chipObstacleSpatialIndex: void 0,
9261
+ _hideRatsNet: this.hideRatsNet
9243
9262
  });
9244
9263
  expandChipsToFitPins(cloned);
9245
9264
  correctPinsInsideChips(cloned);
@@ -9285,7 +9304,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
9285
9304
  if (!this.solved && this.activeSubSolver)
9286
9305
  return this.activeSubSolver.visualize();
9287
9306
  const visualizations = [
9288
- visualizeInputProblem(this.inputProblem),
9307
+ visualizeInputProblem(this.inputProblem, {
9308
+ hideRatsNet: this.hideRatsNet
9309
+ }),
9289
9310
  ...this.pipelineDef.map((p) => this[p.solverName]?.visualize()).filter(Boolean).map((viz, stepIndex) => {
9290
9311
  for (const rect of viz.rects ?? []) {
9291
9312
  rect.step = stepIndex;
@@ -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"
@@ -61,9 +61,8 @@ function definePipelineStep<
61
61
  }
62
62
  }
63
63
 
64
- export interface SchematicTracePipelineSolverParams {
65
- inputProblem: InputProblem
66
- allowLongDistanceTraces?: boolean
64
+ interface Options {
65
+ hideRatsNet?: boolean
67
66
  }
68
67
 
69
68
  export class SchematicTracePipelineSolver extends BaseSolver {
@@ -78,7 +77,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
78
77
  traceCleanupSolver?: TraceCleanupSolver
79
78
  example28Solver?: Example28Solver
80
79
  availableNetOrientationSolver?: AvailableNetOrientationSolver
81
- vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
80
+ railNetLabelCornerPlacementSolver?: RailNetLabelCornerPlacementSolver
82
81
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
83
82
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
84
83
  netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver
@@ -89,6 +88,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
89
88
  firstIterationOfPhase: Record<string, number>
90
89
 
91
90
  inputProblem: InputProblem
91
+ hideRatsNet: boolean
92
92
 
93
93
  pipelineDef = [
94
94
  definePipelineStep(
@@ -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
  ),
@@ -317,8 +318,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
317
318
  ),
318
319
  ]
319
320
 
320
- constructor(inputProblem: InputProblem) {
321
+ constructor(inputProblem: InputProblem, opts?: Options) {
321
322
  super()
323
+ this.hideRatsNet = opts?.hideRatsNet ?? false
322
324
  this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem)
323
325
  this.MAX_ITERATIONS = 1e6
324
326
  this.startTimeOfPhase = {}
@@ -329,8 +331,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
329
331
 
330
332
  override getConstructorParams(): ConstructorParameters<
331
333
  typeof SchematicTracePipelineSolver
332
- >[0] {
333
- return this.inputProblem
334
+ > {
335
+ return [this.inputProblem, { hideRatsNet: this.hideRatsNet }]
334
336
  }
335
337
 
336
338
  currentPipelineStepIndex = 0
@@ -339,6 +341,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
339
341
  const cloned: InputProblem = structuredClone({
340
342
  ...original,
341
343
  _chipObstacleSpatialIndex: undefined,
344
+ _hideRatsNet: this.hideRatsNet,
342
345
  })
343
346
 
344
347
  // First, expand chips so existing pin coordinates sit on or within their edges without shrinking.
@@ -398,7 +401,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
398
401
  return this.activeSubSolver.visualize()
399
402
 
400
403
  const visualizations = [
401
- visualizeInputProblem(this.inputProblem),
404
+ visualizeInputProblem(this.inputProblem, {
405
+ hideRatsNet: this.hideRatsNet,
406
+ }),
402
407
  ...(this.pipelineDef
403
408
  .map((p) => (this as any)[p.solverName]?.visualize())
404
409
  .filter(Boolean)