@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.
- package/.github/workflows/bun-pver-release.yml +7 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +333 -45
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.ts +182 -0
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +67 -25
- package/lib/solvers/AvailableNetOrientationSolver/constants.ts +2 -0
- package/lib/solvers/Example28Solver/labelMovement.ts +71 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +5 -2
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +93 -14
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +12 -4
- package/package.json +5 -1
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +4 -4
- package/tests/bug-reports/bug-report-20260721T221026Z/bug-report-20260721T221026Z.test.ts +11 -0
- package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260815T073240Z/bug-report-20260815T073240Z.test.ts +39 -0
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +237 -0
- package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.json +1052 -0
- package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +33 -0
- package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +4 -4
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +124 -0
- package/tests/repros/assets/repro-board-648-esp12f-section.input.json +537 -0
- package/tests/repros/repro-board-589-regulator-section.test.ts +23 -1
- package/tests/repros/repro-board-648-esp12f-section.test.ts +13 -0
- package/tests/repros/repro161-power-section-autolayout.test.ts +7 -0
- 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 {
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1273
|
-
|
|
1274
|
-
return "chip-collision"
|
|
1275
|
-
}
|
|
1299
|
+
if (this.obstacleIndex.doesTracePathCrossChip(connectorTrace)) {
|
|
1300
|
+
return "chip-collision"
|
|
1276
1301
|
}
|
|
1277
1302
|
|
|
1278
|
-
for (
|
|
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 (
|
|
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
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
55
|
+
for (const label of this.outputNetLabelPlacements) {
|
|
53
56
|
const labelRect: Rect & { strokeColor: string } = {
|
|
54
57
|
center: label.center,
|
|
55
58
|
width: label.width,
|