@tscircuit/schematic-trace-solver 0.0.136 → 0.0.140

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 (25) hide show
  1. package/.github/workflows/bun-pver-release.yml +7 -3
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +333 -45
  4. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.ts +182 -0
  5. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +67 -25
  6. package/lib/solvers/AvailableNetOrientationSolver/constants.ts +2 -0
  7. package/lib/solvers/Example28Solver/labelMovement.ts +71 -0
  8. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +5 -2
  9. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +93 -14
  10. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +12 -4
  11. package/package.json +5 -1
  12. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +4 -4
  13. package/tests/bug-reports/bug-report-20260721T221026Z/bug-report-20260721T221026Z.test.ts +11 -0
  14. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +3 -3
  15. package/tests/bug-reports/bug-report-20260815T073240Z/bug-report-20260815T073240Z.test.ts +39 -0
  16. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +237 -0
  17. package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.json +1052 -0
  18. package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +33 -0
  19. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +4 -4
  20. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +124 -0
  21. package/tests/repros/assets/repro-board-648-esp12f-section.input.json +537 -0
  22. package/tests/repros/repro-board-589-regulator-section.test.ts +23 -1
  23. package/tests/repros/repro-board-648-esp12f-section.test.ts +13 -0
  24. package/tests/repros/repro161-power-section-autolayout.test.ts +7 -0
  25. package/tests/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.test.ts +122 -0
@@ -0,0 +1,182 @@
1
+ import type { Bounds, Point } from "@tscircuit/math-utils"
2
+ import Flatbush from "flatbush"
3
+ import type { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
4
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
5
+ import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
6
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
+ import { EPS } from "./constants"
8
+ import { segmentCrossesBoundsInterior } from "./geometry"
9
+
10
+ export type IndexedTraceSegment = {
11
+ trace: SolvedTracePath
12
+ start: Point
13
+ end: Point
14
+ }
15
+
16
+ export class AvailableNetOrientationObstacleIndex {
17
+ private chipObstacleSpatialIndex: ChipObstacleSpatialIndex
18
+ private labelIndex: Flatbush | null = null
19
+ private traceSegmentIndex: Flatbush | null = null
20
+ private traceSegments: IndexedTraceSegment[] = []
21
+
22
+ constructor(params: {
23
+ chipObstacleSpatialIndex: ChipObstacleSpatialIndex
24
+ netLabelPlacements: NetLabelPlacement[]
25
+ traces: SolvedTracePath[]
26
+ }) {
27
+ this.chipObstacleSpatialIndex = params.chipObstacleSpatialIndex
28
+ this.rebuild(params)
29
+ }
30
+
31
+ rebuild(params: {
32
+ netLabelPlacements: NetLabelPlacement[]
33
+ traces: SolvedTracePath[]
34
+ }) {
35
+ this.labelIndex = this.buildLabelIndex(params.netLabelPlacements)
36
+ this.traceSegments = this.getTraceSegments(params.traces)
37
+ this.traceSegmentIndex = this.buildTraceSegmentIndex(this.traceSegments)
38
+ }
39
+
40
+ getLabelIndicesInBounds(bounds: Bounds) {
41
+ if (!this.labelIndex) return []
42
+ return this.labelIndex.search(
43
+ bounds.minX,
44
+ bounds.minY,
45
+ bounds.maxX,
46
+ bounds.maxY,
47
+ )
48
+ }
49
+
50
+ getTraceSegmentsInBounds(bounds: Bounds) {
51
+ if (!this.traceSegmentIndex) return []
52
+ const searchBounds = getPaddedBounds(bounds, EPS)
53
+ return this.traceSegmentIndex
54
+ .search(
55
+ searchBounds.minX,
56
+ searchBounds.minY,
57
+ searchBounds.maxX,
58
+ searchBounds.maxY,
59
+ )
60
+ .map((segmentIndex) => this.traceSegments[segmentIndex]!)
61
+ }
62
+
63
+ getLabelIndicesNearTracePath(tracePath: Point[]) {
64
+ const labelIndices = new Set<number>()
65
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
66
+ const segmentBounds = getPaddedBounds(
67
+ getSegmentBounds(tracePath[pointIndex]!, tracePath[pointIndex + 1]!),
68
+ EPS,
69
+ )
70
+ for (const labelIndex of this.getLabelIndicesInBounds(segmentBounds)) {
71
+ labelIndices.add(labelIndex)
72
+ }
73
+ }
74
+ return [...labelIndices].sort((a, b) => a - b)
75
+ }
76
+
77
+ doesTracePathCrossChip(tracePath: Point[]) {
78
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
79
+ const start = tracePath[pointIndex]!
80
+ const end = tracePath[pointIndex + 1]!
81
+ const nearbyChips = this.chipObstacleSpatialIndex.getChipsInBounds(
82
+ getPaddedBounds(getSegmentBounds(start, end), EPS),
83
+ )
84
+ for (const chip of nearbyChips) {
85
+ if (segmentCrossesBoundsInterior(start, end, chip.bounds)) return true
86
+ }
87
+ }
88
+ return false
89
+ }
90
+
91
+ doesTraceCrossBoundsInterior(bounds: Bounds) {
92
+ for (const segment of this.getTraceSegmentsInBounds(bounds)) {
93
+ if (segmentCrossesBoundsInterior(segment.start, segment.end, bounds)) {
94
+ return true
95
+ }
96
+ }
97
+ return false
98
+ }
99
+
100
+ doesTraceIntersectBounds(params: {
101
+ bounds: Bounds
102
+ excludedGlobalConnNetId: string
103
+ }) {
104
+ for (const segment of this.getTraceSegmentsInBounds(params.bounds)) {
105
+ if (segment.trace.globalConnNetId === params.excludedGlobalConnNetId) {
106
+ continue
107
+ }
108
+ if (segmentIntersectsRect(segment.start, segment.end, params.bounds)) {
109
+ return true
110
+ }
111
+ }
112
+ return false
113
+ }
114
+
115
+ private buildLabelIndex(netLabelPlacements: NetLabelPlacement[]) {
116
+ if (netLabelPlacements.length === 0) return null
117
+
118
+ const labelIndex = new Flatbush(netLabelPlacements.length)
119
+ for (const label of netLabelPlacements) {
120
+ labelIndex.add(
121
+ label.center.x - label.width / 2,
122
+ label.center.y - label.height / 2,
123
+ label.center.x + label.width / 2,
124
+ label.center.y + label.height / 2,
125
+ )
126
+ }
127
+ labelIndex.finish()
128
+ return labelIndex
129
+ }
130
+
131
+ private getTraceSegments(traces: SolvedTracePath[]) {
132
+ const traceSegments: IndexedTraceSegment[] = []
133
+ for (const trace of traces) {
134
+ for (
135
+ let pointIndex = 0;
136
+ pointIndex < trace.tracePath.length - 1;
137
+ pointIndex++
138
+ ) {
139
+ traceSegments.push({
140
+ trace,
141
+ start: trace.tracePath[pointIndex]!,
142
+ end: trace.tracePath[pointIndex + 1]!,
143
+ })
144
+ }
145
+ }
146
+ return traceSegments
147
+ }
148
+
149
+ private buildTraceSegmentIndex(traceSegments: IndexedTraceSegment[]) {
150
+ if (traceSegments.length === 0) return null
151
+
152
+ const traceSegmentIndex = new Flatbush(traceSegments.length)
153
+ for (const segment of traceSegments) {
154
+ traceSegmentIndex.add(
155
+ Math.min(segment.start.x, segment.end.x),
156
+ Math.min(segment.start.y, segment.end.y),
157
+ Math.max(segment.start.x, segment.end.x),
158
+ Math.max(segment.start.y, segment.end.y),
159
+ )
160
+ }
161
+ traceSegmentIndex.finish()
162
+ return traceSegmentIndex
163
+ }
164
+ }
165
+
166
+ function getSegmentBounds(start: Point, end: Point): Bounds {
167
+ return {
168
+ minX: Math.min(start.x, end.x),
169
+ minY: Math.min(start.y, end.y),
170
+ maxX: Math.max(start.x, end.x),
171
+ maxY: Math.max(start.y, end.y),
172
+ }
173
+ }
174
+
175
+ function getPaddedBounds(bounds: Bounds, padding: number): Bounds {
176
+ return {
177
+ minX: bounds.minX - padding,
178
+ minY: bounds.minY - padding,
179
+ maxX: bounds.maxX + padding,
180
+ maxY: bounds.maxY + padding,
181
+ }
182
+ }
@@ -17,7 +17,12 @@ import type {
17
17
  } from "lib/types/InputProblem"
18
18
  import { dir, type FacingDirection } from "lib/utils/dir"
19
19
  import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
20
- import { EPS, LABEL_SEARCH_STEP, WICK_CLEARANCE } from "./constants"
20
+ import {
21
+ EPS,
22
+ LABEL_SEARCH_STEP,
23
+ MAX_RECORDED_CANDIDATES,
24
+ WICK_CLEARANCE,
25
+ } from "./constants"
21
26
  import {
22
27
  getConnectorTracePath,
23
28
  getMaxSearchDistance,
@@ -27,8 +32,6 @@ import {
27
32
  rangesOverlap,
28
33
  rectsOverlap,
29
34
  simplifyOrthogonalPath,
30
- traceCrossesBoundsInterior,
31
- tracePathCrossesAnyBounds,
32
35
  tracePathIntersectsBounds,
33
36
  } from "./geometry"
34
37
  import { orderRoutedLabelsBeforeOverlappingPortLabels } from "./orderRoutedLabelsBeforeOverlappingPortLabels"
@@ -43,6 +46,7 @@ import type {
43
46
  EvaluatedCandidate,
44
47
  } from "./types"
45
48
  import { visualizeAvailableNetOrientationSolver } from "./visualize"
49
+ import { AvailableNetOrientationObstacleIndex } from "./AvailableNetOrientationObstacleIndex"
46
50
 
47
51
  const LABEL_TRACE_CLEARANCE = 0.1
48
52
 
@@ -64,6 +68,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
64
68
  private chipObstacleSpatialIndex: ChipObstacleSpatialIndex
65
69
  private maxSearchDistance: number
66
70
  private pinMap: Record<string, InputPin & { chipId: string }>
71
+ private obstacleIndex: AvailableNetOrientationObstacleIndex
67
72
 
68
73
  constructor(params: AvailableNetOrientationSolverParams) {
69
74
  super()
@@ -79,6 +84,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
79
84
  params.inputProblem._chipObstacleSpatialIndex ??
80
85
  new ChipObstacleSpatialIndex(params.inputProblem.chips)
81
86
  this.maxSearchDistance = getMaxSearchDistance(params.inputProblem)
87
+ this.obstacleIndex = new AvailableNetOrientationObstacleIndex({
88
+ chipObstacleSpatialIndex: this.chipObstacleSpatialIndex,
89
+ netLabelPlacements: this.outputNetLabelPlacements,
90
+ traces: this.traces,
91
+ })
82
92
  this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices()
83
93
  this.queuedLabelIndices = this.getProcessableLabelIndices()
84
94
  this.setCurrentLabel(this.queuedLabelIndices[0] ?? null)
@@ -309,6 +319,10 @@ export class AvailableNetOrientationSolver extends BaseSolver {
309
319
  ...toNetLabelPlacementPatch(candidate),
310
320
  }
311
321
  this.addConnectorTrace(label, candidate, labelIndex)
322
+ this.obstacleIndex.rebuild({
323
+ netLabelPlacements: this.outputNetLabelPlacements,
324
+ traces: this.traces,
325
+ })
312
326
  }
313
327
 
314
328
  private addConnectorTrace(
@@ -553,7 +567,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
553
567
  anchorY - label.anchorPoint.y,
554
568
  anchorX - label.anchorPoint.x,
555
569
  )
556
- this.currentCandidateResults.push(result)
570
+ this.recordCandidateResult(result)
557
571
  if (result.status !== "valid") continue
558
572
 
559
573
  result.selected = true
@@ -605,7 +619,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
605
619
  labelIndex,
606
620
  "rotate",
607
621
  )
608
- this.currentCandidateResults.push(result)
622
+ this.recordCandidateResult(result)
609
623
  if (result.status === "valid") {
610
624
  result.selected = true
611
625
  return result
@@ -637,7 +651,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
637
651
  labelIndex,
638
652
  "trace-anchor",
639
653
  )
640
- this.currentCandidateResults.push(result)
654
+ this.recordCandidateResult(result)
641
655
 
642
656
  if (result.status === "valid") {
643
657
  result.selected = true
@@ -682,7 +696,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
682
696
  labelIndex,
683
697
  "outward-trace-anchor",
684
698
  )
685
- this.currentCandidateResults.push(preservedColumnResult)
699
+ this.recordCandidateResult(preservedColumnResult)
686
700
  if (preservedColumnResult.status === "valid") {
687
701
  preservedColumnResult.selected = true
688
702
  return preservedColumnResult
@@ -716,7 +730,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
716
730
  "outward-trace-anchor",
717
731
  distance,
718
732
  )
719
- this.currentCandidateResults.push(result)
733
+ this.recordCandidateResult(result)
720
734
 
721
735
  if (result.status === "valid") {
722
736
  result.selected = true
@@ -891,7 +905,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
891
905
  distance,
892
906
  outwardDistance,
893
907
  )
894
- this.currentCandidateResults.push(result)
908
+ this.recordCandidateResult(result)
895
909
 
896
910
  if (result.status === "valid") {
897
911
  result.selected = true
@@ -926,6 +940,19 @@ export class AvailableNetOrientationSolver extends BaseSolver {
926
940
  }
927
941
  }
928
942
 
943
+ private recordCandidateResult(candidate: EvaluatedCandidate) {
944
+ this.stats.candidateEvaluations = (this.stats.candidateEvaluations ?? 0) + 1
945
+ if (this.currentCandidateResults.length < MAX_RECORDED_CANDIDATES) {
946
+ this.currentCandidateResults.push(candidate)
947
+ } else if (candidate.status === "valid") {
948
+ this.currentCandidateResults[MAX_RECORDED_CANDIDATES - 1] = candidate
949
+ }
950
+ this.stats.maxRecordedCandidates = Math.max(
951
+ this.stats.maxRecordedCandidates ?? 0,
952
+ this.currentCandidateResults.length,
953
+ )
954
+ }
955
+
929
956
  private getCandidateConnectorTrace(
930
957
  label: NetLabelPlacement,
931
958
  candidate: Pick<
@@ -1269,13 +1296,13 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1269
1296
  labelIndex,
1270
1297
  )
1271
1298
 
1272
- for (const chip of this.chipObstacleSpatialIndex.chips) {
1273
- if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
1274
- return "chip-collision"
1275
- }
1299
+ if (this.obstacleIndex.doesTracePathCrossChip(connectorTrace)) {
1300
+ return "chip-collision"
1276
1301
  }
1277
1302
 
1278
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
1303
+ for (const i of this.obstacleIndex.getLabelIndicesNearTracePath(
1304
+ connectorTrace,
1305
+ )) {
1279
1306
  if (i === labelIndex) continue
1280
1307
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1281
1308
  const otherLabel = this.outputNetLabelPlacements[i]!
@@ -1345,7 +1372,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1345
1372
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
1346
1373
  return "text-collision"
1347
1374
  }
1348
- if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
1375
+ if (this.obstacleIndex.doesTraceCrossBoundsInterior(bounds)) {
1349
1376
  return "trace-collision"
1350
1377
  }
1351
1378
  if (this.isTraceTooCloseToLabel(bounds, label)) {
@@ -1361,14 +1388,10 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1361
1388
  if (!this.shouldCheckTraceClearanceForLabel(label)) return false
1362
1389
 
1363
1390
  const clearanceBounds = this.getLabelTraceClearanceBounds(bounds)
1364
- for (const trace of Object.values(this.traceMap)) {
1365
- if (trace.globalConnNetId === label.globalConnNetId) continue
1366
- if (tracePathIntersectsBounds(trace.tracePath, clearanceBounds)) {
1367
- return true
1368
- }
1369
- }
1370
-
1371
- return false
1391
+ return this.obstacleIndex.doesTraceIntersectBounds({
1392
+ bounds: clearanceBounds,
1393
+ excludedGlobalConnNetId: label.globalConnNetId,
1394
+ })
1372
1395
  }
1373
1396
 
1374
1397
  private getLabelTraceClearanceBounds(bounds: Bounds): Bounds {
@@ -1496,7 +1519,17 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1496
1519
  }
1497
1520
 
1498
1521
  private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
1499
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
1522
+ const searchBounds = {
1523
+ minX: bounds.minX - LABEL_SEARCH_STEP,
1524
+ minY: bounds.minY - LABEL_SEARCH_STEP,
1525
+ maxX: bounds.maxX + LABEL_SEARCH_STEP,
1526
+ maxY: bounds.maxY + LABEL_SEARCH_STEP,
1527
+ }
1528
+ const nearbyLabelIndices = this.obstacleIndex
1529
+ .getLabelIndicesInBounds(searchBounds)
1530
+ .sort((a, b) => a - b)
1531
+
1532
+ for (const i of nearbyLabelIndices) {
1500
1533
  if (i === labelIndex) continue
1501
1534
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1502
1535
  const label = this.outputNetLabelPlacements[i]!
@@ -1515,7 +1548,16 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1515
1548
  }
1516
1549
 
1517
1550
  private sharesChipBoundary(bounds: Bounds) {
1518
- for (const chip of this.chipObstacleSpatialIndex.chips) {
1551
+ const boundarySearchBounds = {
1552
+ minX: bounds.minX - WICK_CLEARANCE - EPS,
1553
+ minY: bounds.minY - WICK_CLEARANCE - EPS,
1554
+ maxX: bounds.maxX + WICK_CLEARANCE + EPS,
1555
+ maxY: bounds.maxY + WICK_CLEARANCE + EPS,
1556
+ }
1557
+ const nearbyChips =
1558
+ this.chipObstacleSpatialIndex.getChipsInBounds(boundarySearchBounds)
1559
+
1560
+ for (const chip of nearbyChips) {
1519
1561
  const chipBounds = chip.bounds
1520
1562
  const adjacentToVerticalSide =
1521
1563
  Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS ||
@@ -1,4 +1,6 @@
1
1
  export const LABEL_SEARCH_STEP = 0.05
2
+ // Keep failed searches from retaining an unbounded visualization history.
3
+ export const MAX_RECORDED_CANDIDATES = 2_000
2
4
  export const WICK_CLEARANCE = 0.001
3
5
  export const EPS = 1e-9
4
6
  export const TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS
@@ -1,6 +1,7 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
2
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
3
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
4
5
  import { getMovedAnchorPointForReroute } from "./getMovedAnchorPointForReroute"
5
6
  import { isLabelAttachedToTrace } from "./isLabelAttachedToTrace"
6
7
 
@@ -39,3 +40,73 @@ export const moveAttachedLabelsToReroutedTrace = ({
39
40
  },
40
41
  }
41
42
  })
43
+
44
+ export const moveNetLabelConnectorsToReroutedTraces = ({
45
+ originalTraces,
46
+ reroutedTraces,
47
+ netLabelPlacements,
48
+ }: {
49
+ originalTraces: SolvedTracePath[]
50
+ reroutedTraces: SolvedTracePath[]
51
+ netLabelPlacements: NetLabelPlacement[]
52
+ }) => {
53
+ const traces = [...reroutedTraces]
54
+ const reroutedTraceMap = new Map(
55
+ reroutedTraces.map((trace) => [trace.mspPairId, trace]),
56
+ )
57
+
58
+ const labels = netLabelPlacements.map((label) => {
59
+ const originalHostTrace = originalTraces.find((trace) => {
60
+ if (!label.mspConnectionPairIds.includes(trace.mspPairId)) return false
61
+ const reroutedTrace = reroutedTraceMap.get(trace.mspPairId)
62
+ if (!reroutedTrace) return false
63
+ return reroutedTrace.tracePath !== trace.tracePath
64
+ })
65
+ if (!originalHostTrace) return label
66
+
67
+ const reroutedHostTrace = reroutedTraceMap.get(originalHostTrace.mspPairId)!
68
+ const connectorIndex = originalTraces.findIndex((trace) => {
69
+ if (trace.mspPairId === originalHostTrace.mspPairId) return false
70
+ if (trace.globalConnNetId !== label.globalConnNetId) return false
71
+ if (!tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
72
+ return false
73
+ }
74
+ return label.pinIds.every((pinId) => trace.pinIds.includes(pinId))
75
+ })
76
+ if (connectorIndex < 0) return label
77
+
78
+ const connector = originalTraces[connectorIndex]!
79
+ const originalJunction = connector.tracePath.find((point) =>
80
+ tracePathContainsPoint(originalHostTrace.tracePath, point),
81
+ )
82
+ if (!originalJunction) return label
83
+
84
+ const reroutedJunction = getMovedAnchorPointForReroute(
85
+ originalJunction,
86
+ originalHostTrace.tracePath,
87
+ reroutedHostTrace.tracePath,
88
+ )
89
+ if (!reroutedJunction) return label
90
+
91
+ const delta = {
92
+ x: reroutedJunction.x - originalJunction.x,
93
+ y: reroutedJunction.y - originalJunction.y,
94
+ }
95
+ const movedConnector = {
96
+ ...connector,
97
+ tracePath: connector.tracePath.map((point) => ({
98
+ x: point.x + delta.x,
99
+ y: point.y + delta.y,
100
+ })),
101
+ }
102
+ traces[connectorIndex] = movedConnector
103
+ return moveAttachedLabelsToReroutedTrace({
104
+ trace: connector,
105
+ originalTracePath: connector.tracePath,
106
+ reroutedTracePath: movedConnector.tracePath,
107
+ netLabelPlacements: [label],
108
+ })[0]!
109
+ })
110
+
111
+ return { traces, netLabelPlacements: labels }
112
+ }
@@ -17,16 +17,19 @@ interface SameNetJunctionAlignmentSolverInput {
17
17
  export class SameNetJunctionAlignmentSolver extends BaseSolver {
18
18
  private input: SameNetJunctionAlignmentSolverInput
19
19
  outputTraces: SolvedTracePath[]
20
+ outputNetLabelPlacements: NetLabelPlacement[]
20
21
 
21
22
  constructor(input: SameNetJunctionAlignmentSolverInput) {
22
23
  super()
23
24
  this.input = input
24
25
  this.outputTraces = input.traces
26
+ this.outputNetLabelPlacements = input.netLabelPlacements
25
27
  }
26
28
 
27
29
  override _step() {
28
30
  const result = alignSameNetJunctions(this.input)
29
31
  this.outputTraces = result.traces
32
+ this.outputNetLabelPlacements = result.netLabelPlacements
30
33
  this.stats.alignedJunctionCount = result.alignedJunctionCount
31
34
  this.solved = true
32
35
  }
@@ -34,7 +37,7 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
34
37
  getOutput() {
35
38
  return {
36
39
  traces: this.outputTraces,
37
- netLabelPlacements: this.input.netLabelPlacements,
40
+ netLabelPlacements: this.outputNetLabelPlacements,
38
41
  }
39
42
  }
40
43
 
@@ -49,7 +52,7 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
49
52
  strokeColor: "purple",
50
53
  })
51
54
  }
52
- for (const label of this.input.netLabelPlacements) {
55
+ for (const label of this.outputNetLabelPlacements) {
53
56
  const labelRect: Rect & { strokeColor: string } = {
54
57
  center: label.center,
55
58
  width: label.width,