@tscircuit/schematic-trace-solver 0.0.92 → 0.0.94

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.
package/dist/index.d.ts CHANGED
@@ -194,7 +194,6 @@ declare class SchematicTraceSingleLineSolver2 extends BaseSolver {
194
194
  private axisOfSegment;
195
195
  private pathLength;
196
196
  private getPinBandPenalty;
197
- private pathCost;
198
197
  private isSegmentOutsidePinBand;
199
198
  _step(): void;
200
199
  visualize(): GraphicsObject;
@@ -650,6 +649,13 @@ type TracePathScore = {
650
649
  labelIntersections: number;
651
650
  labelHugDistance: number;
652
651
  traceIntersections: number;
652
+ /**
653
+ * Total length the path runs along a chip boundary. A soft quality penalty
654
+ * (traces hugging a chip edge look bad) that is only preferred against once
655
+ * the more important factors above are equal, so a boundary-hugging route is
656
+ * still chosen over one that actually overlaps a label or another trace.
657
+ */
658
+ chipBoundaryOverlap: number;
653
659
  pathLength: number;
654
660
  };
655
661
  type RerouteCandidateResult = {
package/dist/index.js CHANGED
@@ -1112,9 +1112,6 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1112
1112
  }
1113
1113
  return penalty;
1114
1114
  }
1115
- pathCost(path) {
1116
- return this.pathLength(path) + this.getPinBandPenalty(path);
1117
- }
1118
1115
  isSegmentOutsidePinBand(a, b) {
1119
1116
  if (isHorizontal(a, b)) {
1120
1117
  return a.y <= this.aabb.minY || a.y >= this.aabb.maxY;
@@ -1210,8 +1207,12 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1210
1207
  this.visited.add(key);
1211
1208
  const nextSet = new Set(collisionRects);
1212
1209
  nextSet.add(rect);
1213
- const len = this.pathCost(newPath);
1214
- newStates.push({ path: newPath, collisionRects: nextSet, len });
1210
+ newStates.push({
1211
+ path: newPath,
1212
+ collisionRects: nextSet,
1213
+ length: this.pathLength(newPath),
1214
+ pinBandPenalty: this.getPinBandPenalty(newPath)
1215
+ });
1215
1216
  };
1216
1217
  for (const coord of candidates) {
1217
1218
  addShiftedCandidate(segIndex, axis, coord);
@@ -1243,7 +1244,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1243
1244
  addShiftedCandidate(adjacentSegIndex, adjacentAxis, coord);
1244
1245
  }
1245
1246
  }
1246
- newStates.sort((a2, b2) => a2.len - b2.len);
1247
+ newStates.sort(
1248
+ (a2, b2) => a2.length - b2.length || a2.pinBandPenalty - b2.pinBandPenalty
1249
+ );
1247
1250
  for (const st of newStates) {
1248
1251
  this.queue.push({ path: st.path, collisionRects: st.collisionRects });
1249
1252
  }
@@ -5627,6 +5630,19 @@ var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
5627
5630
  }
5628
5631
  return false;
5629
5632
  };
5633
+ var getChipBoundaryOverlap = (path, chipObstacles) => {
5634
+ let total = 0;
5635
+ for (let i = 0; i < path.length - 1; i++) {
5636
+ const start = path[i];
5637
+ const end = path[i + 1];
5638
+ for (const obstacle of chipObstacles) {
5639
+ if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
5640
+ const overlap = getSegmentOverlapWithRectSpan(start, end, obstacle);
5641
+ if (overlap > EPS7) total += overlap;
5642
+ }
5643
+ }
5644
+ return total;
5645
+ };
5630
5646
  var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5631
5647
  const isVertical4 = Math.abs(start.x - end.x) < EPS7;
5632
5648
  if (isVertical4) {
@@ -5637,6 +5653,30 @@ var getSegmentOverlapWithRectSpan = (start, end, rect) => {
5637
5653
 
5638
5654
  // lib/solvers/Example28Solver/reroute.ts
5639
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
+ };
5640
5680
  var findBestReroutePath = ({
5641
5681
  trace,
5642
5682
  obstacleLabel,
@@ -5658,7 +5698,8 @@ var findBestReroutePath = ({
5658
5698
  obstacleLabel,
5659
5699
  outputTraces,
5660
5700
  outputNetLabelPlacements,
5661
- candidateResults
5701
+ candidateResults,
5702
+ chipObstacles
5662
5703
  });
5663
5704
  markSelectedCandidate(candidateResults, bestPath);
5664
5705
  return { bestPath, candidateResults };
@@ -5674,8 +5715,11 @@ var generateRerouteCandidateResults = ({
5674
5715
  const seen = /* @__PURE__ */ new Set();
5675
5716
  const candidateResults = [];
5676
5717
  const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
5677
- trace,
5678
- label
5718
+ {
5719
+ trace,
5720
+ label,
5721
+ outputTraces
5722
+ }
5679
5723
  );
5680
5724
  if (horizontalSegmentPushCandidate) {
5681
5725
  candidateResults.push(
@@ -5745,14 +5789,6 @@ var createCandidateResult = ({
5745
5789
  selected: false
5746
5790
  };
5747
5791
  }
5748
- if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
5749
- return {
5750
- path,
5751
- status: "chip-collision",
5752
- usesHorizontalSegmentPush,
5753
- selected: false
5754
- };
5755
- }
5756
5792
  return {
5757
5793
  path,
5758
5794
  score: scoreTracePath({
@@ -5760,7 +5796,8 @@ var createCandidateResult = ({
5760
5796
  tracePath: path,
5761
5797
  obstacleLabel,
5762
5798
  outputTraces,
5763
- outputNetLabelPlacements
5799
+ outputNetLabelPlacements,
5800
+ chipObstacles
5764
5801
  }),
5765
5802
  status: "valid",
5766
5803
  usesHorizontalSegmentPush,
@@ -5850,7 +5887,8 @@ var selectBestReroutePath = ({
5850
5887
  obstacleLabel,
5851
5888
  outputTraces,
5852
5889
  outputNetLabelPlacements,
5853
- candidateResults
5890
+ candidateResults,
5891
+ chipObstacles
5854
5892
  }) => {
5855
5893
  for (const candidate of candidateResults) {
5856
5894
  if (!candidate.usesHorizontalSegmentPush) continue;
@@ -5864,7 +5902,8 @@ var selectBestReroutePath = ({
5864
5902
  tracePath: trace.tracePath,
5865
5903
  obstacleLabel,
5866
5904
  outputTraces,
5867
- outputNetLabelPlacements
5905
+ outputNetLabelPlacements,
5906
+ chipObstacles
5868
5907
  });
5869
5908
  for (const candidate of candidateResults) {
5870
5909
  if (candidate.status !== "valid" || !candidate.score) continue;
@@ -5874,7 +5913,11 @@ var selectBestReroutePath = ({
5874
5913
  }
5875
5914
  return bestPath;
5876
5915
  };
5877
- var generateHorizontalSegmentPushCandidate = (trace, label) => {
5916
+ var generateHorizontalSegmentPushCandidate = ({
5917
+ trace,
5918
+ label,
5919
+ outputTraces
5920
+ }) => {
5878
5921
  const labelDirection = dir(label.orientation);
5879
5922
  if (labelDirection.x === 0) return null;
5880
5923
  const path = trace.tracePath;
@@ -5899,6 +5942,20 @@ var generateHorizontalSegmentPushCandidate = (trace, label) => {
5899
5942
  if (labelDirection.x > 0) {
5900
5943
  segmentPushX = bounds.maxX + LABEL_CLEARANCE;
5901
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
+ }
5902
5959
  const segmentPushStartY = getClearedHorizontalY({
5903
5960
  start: previousAnchor,
5904
5961
  end: { x: segmentPushX, y: verticalStart.y },
@@ -5950,7 +6007,8 @@ var scoreTracePath = ({
5950
6007
  tracePath,
5951
6008
  obstacleLabel,
5952
6009
  outputTraces,
5953
- outputNetLabelPlacements
6010
+ outputNetLabelPlacements,
6011
+ chipObstacles
5954
6012
  }) => {
5955
6013
  const candidateTrace = { ...trace, tracePath };
5956
6014
  return {
@@ -5960,6 +6018,7 @@ var scoreTracePath = ({
5960
6018
  }).length,
5961
6019
  labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
5962
6020
  traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
6021
+ chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
5963
6022
  pathLength: getPathLength(tracePath)
5964
6023
  };
5965
6024
  };
@@ -5972,6 +6031,14 @@ var countTraceIntersections = (trace, outputTraces) => {
5972
6031
  return count;
5973
6032
  };
5974
6033
  var isBetterScore = (score, bestScore) => {
6034
+ const scoreHasLabelOverlap = score.labelIntersections > 0;
6035
+ const bestHasLabelOverlap = bestScore.labelIntersections > 0;
6036
+ if (scoreHasLabelOverlap !== bestHasLabelOverlap) {
6037
+ return !scoreHasLabelOverlap;
6038
+ }
6039
+ if (score.chipBoundaryOverlap !== bestScore.chipBoundaryOverlap) {
6040
+ return score.chipBoundaryOverlap < bestScore.chipBoundaryOverlap;
6041
+ }
5975
6042
  if (score.labelIntersections !== bestScore.labelIntersections) {
5976
6043
  return score.labelIntersections < bestScore.labelIntersections;
5977
6044
  }
@@ -20,6 +20,24 @@ export const doesPathRunAlongChipBoundary = (
20
20
  return false
21
21
  }
22
22
 
23
+ /** Total length of the path that runs along (overlapping) a chip boundary. */
24
+ export const getChipBoundaryOverlap = (
25
+ path: Point[],
26
+ chipObstacles: ChipObstacle[],
27
+ ) => {
28
+ let total = 0
29
+ for (let i = 0; i < path.length - 1; i++) {
30
+ const start = path[i]!
31
+ const end = path[i + 1]!
32
+ for (const obstacle of chipObstacles) {
33
+ if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue
34
+ const overlap = getSegmentOverlapWithRectSpan(start, end, obstacle)
35
+ if (overlap > EPS) total += overlap
36
+ }
37
+ }
38
+ return total
39
+ }
40
+
23
41
  const getSegmentOverlapWithRectSpan = (
24
42
  start: Point,
25
43
  end: Point,
@@ -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 { doesPathRunAlongChipBoundary } 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,
@@ -52,6 +89,7 @@ export const findBestReroutePath = ({
52
89
  outputTraces,
53
90
  outputNetLabelPlacements,
54
91
  candidateResults,
92
+ chipObstacles,
55
93
  })
56
94
 
57
95
  markSelectedCandidate(candidateResults, bestPath)
@@ -77,8 +115,11 @@ export const generateRerouteCandidateResults = ({
77
115
  const seen = new Set<string>()
78
116
  const candidateResults: RerouteCandidateResult[] = []
79
117
  const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
80
- trace,
81
- label,
118
+ {
119
+ trace,
120
+ label,
121
+ outputTraces,
122
+ },
82
123
  )
83
124
 
84
125
  if (horizontalSegmentPushCandidate) {
@@ -166,15 +207,11 @@ const createCandidateResult = ({
166
207
  }
167
208
  }
168
209
 
169
- if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
170
- return {
171
- path,
172
- status: "chip-collision",
173
- usesHorizontalSegmentPush,
174
- selected: false,
175
- }
176
- }
177
-
210
+ // Note: a path that merely runs along a chip boundary is NOT rejected here.
211
+ // It is a valid route, just a lower-quality one — scoreTracePath penalizes it
212
+ // via chipBoundaryOverlap so it loses only to routes that are otherwise as
213
+ // good. Hard-rejecting it discarded the best available route when every
214
+ // alternative overlapped a label or another trace.
178
215
  return {
179
216
  path,
180
217
  score: scoreTracePath({
@@ -183,6 +220,7 @@ const createCandidateResult = ({
183
220
  obstacleLabel,
184
221
  outputTraces,
185
222
  outputNetLabelPlacements,
223
+ chipObstacles,
186
224
  }),
187
225
  status: "valid",
188
226
  usesHorizontalSegmentPush,
@@ -293,12 +331,14 @@ const selectBestReroutePath = ({
293
331
  outputTraces,
294
332
  outputNetLabelPlacements,
295
333
  candidateResults,
334
+ chipObstacles,
296
335
  }: {
297
336
  trace: SolvedTracePath
298
337
  obstacleLabel: NetLabelPlacement
299
338
  outputTraces: SolvedTracePath[]
300
339
  outputNetLabelPlacements: NetLabelPlacement[]
301
340
  candidateResults: RerouteCandidateResult[]
341
+ chipObstacles: ChipObstacle[]
302
342
  }) => {
303
343
  for (const candidate of candidateResults) {
304
344
  if (!candidate.usesHorizontalSegmentPush) continue
@@ -314,6 +354,7 @@ const selectBestReroutePath = ({
314
354
  obstacleLabel,
315
355
  outputTraces,
316
356
  outputNetLabelPlacements,
357
+ chipObstacles,
317
358
  })
318
359
 
319
360
  for (const candidate of candidateResults) {
@@ -327,10 +368,15 @@ const selectBestReroutePath = ({
327
368
  return bestPath
328
369
  }
329
370
 
330
- const generateHorizontalSegmentPushCandidate = (
331
- trace: SolvedTracePath,
332
- label: NetLabelPlacement,
333
- ): Point[] | null => {
371
+ const generateHorizontalSegmentPushCandidate = ({
372
+ trace,
373
+ label,
374
+ outputTraces,
375
+ }: {
376
+ trace: SolvedTracePath
377
+ label: NetLabelPlacement
378
+ outputTraces: SolvedTracePath[]
379
+ }): Point[] | null => {
334
380
  const labelDirection = dir(label.orientation)
335
381
  if (labelDirection.x === 0) return null
336
382
 
@@ -366,6 +412,25 @@ const generateHorizontalSegmentPushCandidate = (
366
412
  if (labelDirection.x > 0) {
367
413
  segmentPushX = bounds.maxX + LABEL_CLEARANCE
368
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
+ }
369
434
  const segmentPushStartY = getClearedHorizontalY({
370
435
  start: previousAnchor,
371
436
  end: { x: segmentPushX, y: verticalStart.y },
@@ -437,12 +502,14 @@ const scoreTracePath = ({
437
502
  obstacleLabel,
438
503
  outputTraces,
439
504
  outputNetLabelPlacements,
505
+ chipObstacles,
440
506
  }: {
441
507
  trace: SolvedTracePath
442
508
  tracePath: Point[]
443
509
  obstacleLabel: NetLabelPlacement
444
510
  outputTraces: SolvedTracePath[]
445
511
  outputNetLabelPlacements: NetLabelPlacement[]
512
+ chipObstacles: ChipObstacle[]
446
513
  }): TracePathScore => {
447
514
  const candidateTrace = { ...trace, tracePath }
448
515
  return {
@@ -452,6 +519,7 @@ const scoreTracePath = ({
452
519
  }).length,
453
520
  labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
454
521
  traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
522
+ chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
455
523
  pathLength: getPathLength(tracePath),
456
524
  }
457
525
  }
@@ -469,6 +537,21 @@ const countTraceIntersections = (
469
537
  }
470
538
 
471
539
  const isBetterScore = (score: TracePathScore, bestScore: TracePathScore) => {
540
+ // A route with no label overlap is always preferred over one with any. This
541
+ // is separate from the full overlap count below so that a *clean* route wins
542
+ // even if it hugs a chip boundary, while a route that must overlap a label is
543
+ // still steered away from also hugging a boundary.
544
+ const scoreHasLabelOverlap = score.labelIntersections > 0
545
+ const bestHasLabelOverlap = bestScore.labelIntersections > 0
546
+ if (scoreHasLabelOverlap !== bestHasLabelOverlap) {
547
+ // Reached only when exactly one route overlaps a label; the clean one wins.
548
+ return !scoreHasLabelOverlap
549
+ }
550
+ // Boundary hugging is only accepted as the price of a clean route; once a
551
+ // label overlap is unavoidable, avoid hugging a boundary on top of it.
552
+ if (score.chipBoundaryOverlap !== bestScore.chipBoundaryOverlap) {
553
+ return score.chipBoundaryOverlap < bestScore.chipBoundaryOverlap
554
+ }
472
555
  if (score.labelIntersections !== bestScore.labelIntersections) {
473
556
  return score.labelIntersections < bestScore.labelIntersections
474
557
  }
@@ -15,6 +15,13 @@ export type TracePathScore = {
15
15
  labelIntersections: number
16
16
  labelHugDistance: number
17
17
  traceIntersections: number
18
+ /**
19
+ * Total length the path runs along a chip boundary. A soft quality penalty
20
+ * (traces hugging a chip edge look bad) that is only preferred against once
21
+ * the more important factors above are equal, so a boundary-hugging route is
22
+ * still chosen over one that actually overlaps a label or another trace.
23
+ */
24
+ chipBoundaryOverlap: number
18
25
  pathLength: number
19
26
  }
20
27
 
@@ -239,10 +239,6 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
239
239
  return penalty
240
240
  }
241
241
 
242
- private pathCost(path: Point[]): number {
243
- return this.pathLength(path) + this.getPinBandPenalty(path)
244
- }
245
-
246
242
  private isSegmentOutsidePinBand(a: Point, b: Point): boolean {
247
243
  if (isHorizontal(a, b)) {
248
244
  return a.y <= this.aabb.minY || a.y >= this.aabb.maxY
@@ -362,11 +358,15 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
362
358
  candidates.push(...mids)
363
359
  }
364
360
 
365
- // Generate new shifted paths, order by total path length (shorter first)
361
+ // Generate new shifted paths. Order by path length first, then prefer paths
362
+ // that stay out of the pin band. The pin-band preference is a tiebreaker
363
+ // only: it must never make the solver pick a longer route (a flat additive
364
+ // penalty would, discarding a shorter valid route in favor of a detour).
366
365
  const newStates: Array<{
367
366
  path: Point[]
368
367
  collisionRects: Set<ObstacleRect>
369
- len: number
368
+ length: number
369
+ pinBandPenalty: number
370
370
  }> = []
371
371
 
372
372
  const addShiftedCandidate = (
@@ -386,8 +386,12 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
386
386
  this.visited.add(key)
387
387
  const nextSet = new Set(collisionRects)
388
388
  nextSet.add(rect)
389
- const len = this.pathCost(newPath)
390
- newStates.push({ path: newPath, collisionRects: nextSet, len })
389
+ newStates.push({
390
+ path: newPath,
391
+ collisionRects: nextSet,
392
+ length: this.pathLength(newPath),
393
+ pinBandPenalty: this.getPinBandPenalty(newPath),
394
+ })
391
395
  }
392
396
 
393
397
  for (const coord of candidates) {
@@ -430,7 +434,9 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
430
434
  }
431
435
  }
432
436
 
433
- newStates.sort((a, b) => a.len - b.len)
437
+ newStates.sort(
438
+ (a, b) => a.length - b.length || a.pinBandPenalty - b.pinBandPenalty,
439
+ )
434
440
  for (const st of newStates) {
435
441
  this.queue.push({ path: st.path, collisionRects: st.collisionRects })
436
442
  }
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.92",
4
+ "version": "0.0.94",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",