@tscircuit/schematic-trace-solver 0.0.107 → 0.0.109
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 +31 -3
- package/dist/index.js +784 -247
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +3 -1
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +58 -27
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts +1 -10
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateInternalSegmentCollisionDetours.ts +94 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +10 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts +5 -1
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +18 -3
- package/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts +592 -0
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260724T175257Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +5 -9
- package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +5 -5
- package/tests/bug-reports/bug-report-20260708T095725Z/bug-report-20260708T095725Z.test.ts +15 -0
- package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +58 -0
- package/tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.json +138 -0
- package/tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.test.ts +12 -0
- package/tests/examples/__snapshots__/example04.snap.svg +7 -9
- package/tests/examples/__snapshots__/example08.snap.svg +16 -20
- package/tests/examples/__snapshots__/example13.snap.svg +2 -4
- package/tests/examples/__snapshots__/example32.snap.svg +64 -66
- package/tests/examples/__snapshots__/example46.snap.svg +1 -1
- package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +81 -0
- package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +16 -18
- package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +63 -220
- package/tests/repros/assets/repro-netlabel-collision-687.input.json +212 -0
- package/tests/repros/repro-netlabel-collision-687.test.ts +59 -0
- package/tests/repros/repro-tps61222-trace-intersection.test.ts +27 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/generate-internal-segment-collision-detours.test.ts +49 -0
- package/tests/solvers/UnroutedTraceRecoverySolver/paired-junction-recovery.test.ts +49 -0
- package/tests/solvers/UnroutedTraceRecoverySolver/skip-ground.test.ts +30 -0
- package/tests/solvers/UnroutedTraceRecoverySolver/skip-long-connection.test.ts +24 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { distance, doSegmentsIntersect } from "@tscircuit/math-utils"
|
|
3
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
|
+
import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_MAX_MSP_PAIR_DISTANCE,
|
|
7
|
+
type MspConnectionPair,
|
|
8
|
+
} from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
9
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
10
|
+
import {
|
|
11
|
+
findFirstCollision,
|
|
12
|
+
isHorizontal,
|
|
13
|
+
isVertical,
|
|
14
|
+
segmentOverlapsRectBoundary,
|
|
15
|
+
} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
16
|
+
import {
|
|
17
|
+
getObstacleRects,
|
|
18
|
+
type ObstacleRect,
|
|
19
|
+
} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
20
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
21
|
+
import type { InputProblem, PinId } from "lib/types/InputProblem"
|
|
22
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
23
|
+
|
|
24
|
+
const ROUTE_CLEARANCE = 0.2
|
|
25
|
+
const COORDINATE_TOLERANCE = 1e-9
|
|
26
|
+
const GROUND_NET_ID = "GND"
|
|
27
|
+
|
|
28
|
+
const pointsAreEqual = (firstPoint: Point, secondPoint: Point): boolean => {
|
|
29
|
+
return (
|
|
30
|
+
Math.abs(firstPoint.x - secondPoint.x) <= COORDINATE_TOLERANCE &&
|
|
31
|
+
Math.abs(firstPoint.y - secondPoint.y) <= COORDINATE_TOLERANCE
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const removeConsecutiveDuplicatePoints = (path: Point[]): Point[] => {
|
|
36
|
+
const filteredPath: Point[] = []
|
|
37
|
+
for (const point of path) {
|
|
38
|
+
const previousPoint = filteredPath.at(-1)
|
|
39
|
+
if (!previousPoint || !pointsAreEqual(previousPoint, point)) {
|
|
40
|
+
filteredPath.push(point)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return filteredPath
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getPathLength = (path: Point[]): number => {
|
|
47
|
+
let pathLength = 0
|
|
48
|
+
for (let pointIndex = 0; pointIndex < path.length - 1; pointIndex++) {
|
|
49
|
+
const startPoint = path[pointIndex]!
|
|
50
|
+
const endPoint = path[pointIndex + 1]!
|
|
51
|
+
pathLength +=
|
|
52
|
+
Math.abs(endPoint.x - startPoint.x) + Math.abs(endPoint.y - startPoint.y)
|
|
53
|
+
}
|
|
54
|
+
return pathLength
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getEscapePoint = ({
|
|
58
|
+
pin,
|
|
59
|
+
facingDirection,
|
|
60
|
+
}: {
|
|
61
|
+
pin: Point
|
|
62
|
+
facingDirection: FacingDirection
|
|
63
|
+
}): Point => {
|
|
64
|
+
const escapePoint = { x: pin.x, y: pin.y }
|
|
65
|
+
if (facingDirection === "x+") {
|
|
66
|
+
escapePoint.x += ROUTE_CLEARANCE
|
|
67
|
+
}
|
|
68
|
+
if (facingDirection === "x-") {
|
|
69
|
+
escapePoint.x -= ROUTE_CLEARANCE
|
|
70
|
+
}
|
|
71
|
+
if (facingDirection === "y+") {
|
|
72
|
+
escapePoint.y += ROUTE_CLEARANCE
|
|
73
|
+
}
|
|
74
|
+
if (facingDirection === "y-") {
|
|
75
|
+
escapePoint.y -= ROUTE_CLEARANCE
|
|
76
|
+
}
|
|
77
|
+
return escapePoint
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const getOuterBounds = (obstacles: ObstacleRect[]) => {
|
|
81
|
+
return {
|
|
82
|
+
minX: Math.min(...obstacles.map((obstacle) => obstacle.minX)),
|
|
83
|
+
minY: Math.min(...obstacles.map((obstacle) => obstacle.minY)),
|
|
84
|
+
maxX: Math.max(...obstacles.map((obstacle) => obstacle.maxX)),
|
|
85
|
+
maxY: Math.max(...obstacles.map((obstacle) => obstacle.maxY)),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const getPerimeterCandidates = ({
|
|
90
|
+
connectionPair,
|
|
91
|
+
obstacles,
|
|
92
|
+
}: {
|
|
93
|
+
connectionPair: MspConnectionPair
|
|
94
|
+
obstacles: ObstacleRect[]
|
|
95
|
+
}): Point[][] => {
|
|
96
|
+
const [firstPin, secondPin] = connectionPair.pins
|
|
97
|
+
const firstEscapePoint = getEscapePoint({
|
|
98
|
+
pin: firstPin,
|
|
99
|
+
facingDirection: firstPin._facingDirection!,
|
|
100
|
+
})
|
|
101
|
+
const secondEscapePoint = getEscapePoint({
|
|
102
|
+
pin: secondPin,
|
|
103
|
+
facingDirection: secondPin._facingDirection!,
|
|
104
|
+
})
|
|
105
|
+
const outerBounds = getOuterBounds(obstacles)
|
|
106
|
+
const horizontalChannels = [
|
|
107
|
+
outerBounds.minY - ROUTE_CLEARANCE,
|
|
108
|
+
outerBounds.maxY + ROUTE_CLEARANCE,
|
|
109
|
+
]
|
|
110
|
+
const verticalChannels = [
|
|
111
|
+
outerBounds.minX - ROUTE_CLEARANCE,
|
|
112
|
+
outerBounds.maxX + ROUTE_CLEARANCE,
|
|
113
|
+
]
|
|
114
|
+
const candidates: Point[][] = []
|
|
115
|
+
|
|
116
|
+
for (const channelY of horizontalChannels) {
|
|
117
|
+
candidates.push(
|
|
118
|
+
removeConsecutiveDuplicatePoints([
|
|
119
|
+
firstPin,
|
|
120
|
+
firstEscapePoint,
|
|
121
|
+
{ x: firstEscapePoint.x, y: channelY },
|
|
122
|
+
{ x: secondEscapePoint.x, y: channelY },
|
|
123
|
+
secondEscapePoint,
|
|
124
|
+
secondPin,
|
|
125
|
+
]),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const channelX of verticalChannels) {
|
|
130
|
+
candidates.push(
|
|
131
|
+
removeConsecutiveDuplicatePoints([
|
|
132
|
+
firstPin,
|
|
133
|
+
firstEscapePoint,
|
|
134
|
+
{ x: channelX, y: firstEscapePoint.y },
|
|
135
|
+
{ x: channelX, y: secondEscapePoint.y },
|
|
136
|
+
secondEscapePoint,
|
|
137
|
+
secondPin,
|
|
138
|
+
]),
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return candidates.sort(
|
|
143
|
+
(firstPath, secondPath) =>
|
|
144
|
+
getPathLength(firstPath) - getPathLength(secondPath),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const getSegmentMidpoint = (startPoint: Point, endPoint: Point): Point => {
|
|
149
|
+
return {
|
|
150
|
+
x: (startPoint.x + endPoint.x) / 2,
|
|
151
|
+
y: (startPoint.y + endPoint.y) / 2,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const getJunctionPoints = (sameNetTraces: SolvedTracePath[]): Point[] => {
|
|
156
|
+
const junctionPoints: Point[] = []
|
|
157
|
+
for (const trace of sameNetTraces) {
|
|
158
|
+
for (
|
|
159
|
+
let pointIndex = 0;
|
|
160
|
+
pointIndex < trace.tracePath.length - 1;
|
|
161
|
+
pointIndex++
|
|
162
|
+
) {
|
|
163
|
+
const startPoint = trace.tracePath[pointIndex]!
|
|
164
|
+
const endPoint = trace.tracePath[pointIndex + 1]!
|
|
165
|
+
junctionPoints.push(startPoint)
|
|
166
|
+
junctionPoints.push(getSegmentMidpoint(startPoint, endPoint))
|
|
167
|
+
}
|
|
168
|
+
const lastPoint = trace.tracePath.at(-1)
|
|
169
|
+
if (lastPoint) {
|
|
170
|
+
junctionPoints.push(lastPoint)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return junctionPoints
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const getUnconnectedPins = ({
|
|
177
|
+
connectionPair,
|
|
178
|
+
sameNetTraces,
|
|
179
|
+
}: {
|
|
180
|
+
connectionPair: MspConnectionPair
|
|
181
|
+
sameNetTraces: SolvedTracePath[]
|
|
182
|
+
}) => {
|
|
183
|
+
const connectedPinIds = new Set(
|
|
184
|
+
sameNetTraces.flatMap((trace) => trace.pinIds),
|
|
185
|
+
)
|
|
186
|
+
return connectionPair.pins.filter((pin) => !connectedPinIds.has(pin.pinId))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const getJunctionCandidates = ({
|
|
190
|
+
connectionPair,
|
|
191
|
+
sameNetTraces,
|
|
192
|
+
obstacles,
|
|
193
|
+
maxConnectionDistance,
|
|
194
|
+
}: {
|
|
195
|
+
connectionPair: MspConnectionPair
|
|
196
|
+
sameNetTraces: SolvedTracePath[]
|
|
197
|
+
obstacles: ObstacleRect[]
|
|
198
|
+
maxConnectionDistance: number
|
|
199
|
+
}): Point[][] => {
|
|
200
|
+
const outerBounds = getOuterBounds(obstacles)
|
|
201
|
+
const horizontalChannels = [
|
|
202
|
+
outerBounds.minY - ROUTE_CLEARANCE,
|
|
203
|
+
outerBounds.maxY + ROUTE_CLEARANCE,
|
|
204
|
+
]
|
|
205
|
+
const verticalChannels = [
|
|
206
|
+
outerBounds.minX - ROUTE_CLEARANCE,
|
|
207
|
+
outerBounds.maxX + ROUTE_CLEARANCE,
|
|
208
|
+
]
|
|
209
|
+
const candidates: Point[][] = []
|
|
210
|
+
const junctionPoints = getJunctionPoints(sameNetTraces)
|
|
211
|
+
const unconnectedPins = getUnconnectedPins({
|
|
212
|
+
connectionPair,
|
|
213
|
+
sameNetTraces,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
for (const pin of unconnectedPins) {
|
|
217
|
+
const escapePoint = getEscapePoint({
|
|
218
|
+
pin,
|
|
219
|
+
facingDirection: pin._facingDirection!,
|
|
220
|
+
})
|
|
221
|
+
for (const junctionPoint of junctionPoints) {
|
|
222
|
+
if (distance(pin, junctionPoint) > maxConnectionDistance) {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
candidates.push(
|
|
226
|
+
removeConsecutiveDuplicatePoints([
|
|
227
|
+
pin,
|
|
228
|
+
escapePoint,
|
|
229
|
+
{ x: escapePoint.x, y: junctionPoint.y },
|
|
230
|
+
junctionPoint,
|
|
231
|
+
]),
|
|
232
|
+
)
|
|
233
|
+
candidates.push(
|
|
234
|
+
removeConsecutiveDuplicatePoints([
|
|
235
|
+
pin,
|
|
236
|
+
escapePoint,
|
|
237
|
+
{ x: junctionPoint.x, y: escapePoint.y },
|
|
238
|
+
junctionPoint,
|
|
239
|
+
]),
|
|
240
|
+
)
|
|
241
|
+
for (const channelY of horizontalChannels) {
|
|
242
|
+
candidates.push(
|
|
243
|
+
removeConsecutiveDuplicatePoints([
|
|
244
|
+
pin,
|
|
245
|
+
escapePoint,
|
|
246
|
+
{ x: escapePoint.x, y: channelY },
|
|
247
|
+
{ x: junctionPoint.x, y: channelY },
|
|
248
|
+
junctionPoint,
|
|
249
|
+
]),
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
for (const channelX of verticalChannels) {
|
|
253
|
+
candidates.push(
|
|
254
|
+
removeConsecutiveDuplicatePoints([
|
|
255
|
+
pin,
|
|
256
|
+
escapePoint,
|
|
257
|
+
{ x: channelX, y: escapePoint.y },
|
|
258
|
+
{ x: channelX, y: junctionPoint.y },
|
|
259
|
+
junctionPoint,
|
|
260
|
+
]),
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return candidates.sort(
|
|
267
|
+
(firstPath, secondPath) =>
|
|
268
|
+
getPathLength(firstPath) - getPathLength(secondPath),
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const pathCollidesWithObstacles = ({
|
|
273
|
+
path,
|
|
274
|
+
obstacles,
|
|
275
|
+
connectionPair,
|
|
276
|
+
rejectComponentBoundaryTravel,
|
|
277
|
+
}: {
|
|
278
|
+
path: Point[]
|
|
279
|
+
obstacles: ObstacleRect[]
|
|
280
|
+
connectionPair: MspConnectionPair
|
|
281
|
+
rejectComponentBoundaryTravel: boolean
|
|
282
|
+
}): boolean => {
|
|
283
|
+
const firstPathPoint = path[0]!
|
|
284
|
+
const lastPathPoint = path.at(-1)!
|
|
285
|
+
const firstPathPin = connectionPair.pins.find((pin) =>
|
|
286
|
+
pointsAreEqual(pin, firstPathPoint),
|
|
287
|
+
)
|
|
288
|
+
const lastPathPin = connectionPair.pins.find((pin) =>
|
|
289
|
+
pointsAreEqual(pin, lastPathPoint),
|
|
290
|
+
)
|
|
291
|
+
const pathConnectsPairPins =
|
|
292
|
+
firstPathPin !== undefined && lastPathPin !== undefined
|
|
293
|
+
const firstChipObstacle = obstacles.find(
|
|
294
|
+
(obstacle) =>
|
|
295
|
+
obstacle.kind === "chip" && obstacle.chipId === firstPathPin?.chipId,
|
|
296
|
+
)
|
|
297
|
+
const secondChipObstacle = obstacles.find(
|
|
298
|
+
(obstacle) =>
|
|
299
|
+
obstacle.kind === "chip" && obstacle.chipId === lastPathPin?.chipId,
|
|
300
|
+
)
|
|
301
|
+
if (
|
|
302
|
+
rejectComponentBoundaryTravel &&
|
|
303
|
+
firstChipObstacle &&
|
|
304
|
+
segmentOverlapsRectBoundary(path[0]!, path[1]!, firstChipObstacle)
|
|
305
|
+
) {
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
308
|
+
if (
|
|
309
|
+
rejectComponentBoundaryTravel &&
|
|
310
|
+
secondChipObstacle &&
|
|
311
|
+
segmentOverlapsRectBoundary(
|
|
312
|
+
path[path.length - 2]!,
|
|
313
|
+
path[path.length - 1]!,
|
|
314
|
+
secondChipObstacle,
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
const collision = findFirstCollision(path, obstacles, {
|
|
320
|
+
excludeRectsForSegment: (segmentIndex) => {
|
|
321
|
+
const excludedObstacles = new Set<ObstacleRect>()
|
|
322
|
+
if (segmentIndex === 0 && firstChipObstacle) {
|
|
323
|
+
excludedObstacles.add(firstChipObstacle)
|
|
324
|
+
}
|
|
325
|
+
if (
|
|
326
|
+
pathConnectsPairPins &&
|
|
327
|
+
segmentIndex === path.length - 2 &&
|
|
328
|
+
secondChipObstacle
|
|
329
|
+
) {
|
|
330
|
+
excludedObstacles.add(secondChipObstacle)
|
|
331
|
+
}
|
|
332
|
+
return excludedObstacles
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
return collision !== null
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const hasParallelFailedConnection = ({
|
|
339
|
+
connectionPair,
|
|
340
|
+
failedConnectionPairs,
|
|
341
|
+
}: {
|
|
342
|
+
connectionPair: MspConnectionPair
|
|
343
|
+
failedConnectionPairs: MspConnectionPair[]
|
|
344
|
+
}): boolean => {
|
|
345
|
+
const connectionChipIds = new Set(
|
|
346
|
+
connectionPair.pins.map((pin) => pin.chipId),
|
|
347
|
+
)
|
|
348
|
+
return failedConnectionPairs.some((otherConnectionPair) => {
|
|
349
|
+
if (otherConnectionPair.mspPairId === connectionPair.mspPairId) {
|
|
350
|
+
return false
|
|
351
|
+
}
|
|
352
|
+
return otherConnectionPair.pins.every((pin) =>
|
|
353
|
+
connectionChipIds.has(pin.chipId),
|
|
354
|
+
)
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const segmentsOverlapBeyondEndpoint = ({
|
|
359
|
+
firstStart,
|
|
360
|
+
firstEnd,
|
|
361
|
+
secondStart,
|
|
362
|
+
secondEnd,
|
|
363
|
+
}: {
|
|
364
|
+
firstStart: Point
|
|
365
|
+
firstEnd: Point
|
|
366
|
+
secondStart: Point
|
|
367
|
+
secondEnd: Point
|
|
368
|
+
}): boolean => {
|
|
369
|
+
if (
|
|
370
|
+
isHorizontal(firstStart, firstEnd) &&
|
|
371
|
+
isHorizontal(secondStart, secondEnd)
|
|
372
|
+
) {
|
|
373
|
+
const overlapLength =
|
|
374
|
+
Math.min(
|
|
375
|
+
Math.max(firstStart.x, firstEnd.x),
|
|
376
|
+
Math.max(secondStart.x, secondEnd.x),
|
|
377
|
+
) -
|
|
378
|
+
Math.max(
|
|
379
|
+
Math.min(firstStart.x, firstEnd.x),
|
|
380
|
+
Math.min(secondStart.x, secondEnd.x),
|
|
381
|
+
)
|
|
382
|
+
return overlapLength > COORDINATE_TOLERANCE
|
|
383
|
+
}
|
|
384
|
+
if (isVertical(firstStart, firstEnd) && isVertical(secondStart, secondEnd)) {
|
|
385
|
+
const overlapLength =
|
|
386
|
+
Math.min(
|
|
387
|
+
Math.max(firstStart.y, firstEnd.y),
|
|
388
|
+
Math.max(secondStart.y, secondEnd.y),
|
|
389
|
+
) -
|
|
390
|
+
Math.max(
|
|
391
|
+
Math.min(firstStart.y, firstEnd.y),
|
|
392
|
+
Math.min(secondStart.y, secondEnd.y),
|
|
393
|
+
)
|
|
394
|
+
return overlapLength > COORDINATE_TOLERANCE
|
|
395
|
+
}
|
|
396
|
+
return false
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const getAllowedJunctionPoints = ({
|
|
400
|
+
connectionPair,
|
|
401
|
+
existingTrace,
|
|
402
|
+
}: {
|
|
403
|
+
connectionPair: MspConnectionPair
|
|
404
|
+
existingTrace: SolvedTracePath
|
|
405
|
+
}): Point[] => {
|
|
406
|
+
const existingPinIds = new Set<PinId>(existingTrace.pinIds)
|
|
407
|
+
return connectionPair.pins.filter((pin) => existingPinIds.has(pin.pinId))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const pathCrossesExistingTraces = ({
|
|
411
|
+
path,
|
|
412
|
+
connectionPair,
|
|
413
|
+
existingTraces,
|
|
414
|
+
}: {
|
|
415
|
+
path: Point[]
|
|
416
|
+
connectionPair: MspConnectionPair
|
|
417
|
+
existingTraces: SolvedTracePath[]
|
|
418
|
+
}): boolean => {
|
|
419
|
+
for (const existingTrace of existingTraces) {
|
|
420
|
+
if (existingTrace.globalConnNetId === connectionPair.globalConnNetId) {
|
|
421
|
+
continue
|
|
422
|
+
}
|
|
423
|
+
const allowedJunctionPoints = getAllowedJunctionPoints({
|
|
424
|
+
connectionPair,
|
|
425
|
+
existingTrace,
|
|
426
|
+
})
|
|
427
|
+
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
|
|
428
|
+
const pathStart = path[pathIndex]!
|
|
429
|
+
const pathEnd = path[pathIndex + 1]!
|
|
430
|
+
for (
|
|
431
|
+
let traceIndex = 0;
|
|
432
|
+
traceIndex < existingTrace.tracePath.length - 1;
|
|
433
|
+
traceIndex++
|
|
434
|
+
) {
|
|
435
|
+
const traceStart = existingTrace.tracePath[traceIndex]!
|
|
436
|
+
const traceEnd = existingTrace.tracePath[traceIndex + 1]!
|
|
437
|
+
if (!doSegmentsIntersect(pathStart, pathEnd, traceStart, traceEnd)) {
|
|
438
|
+
continue
|
|
439
|
+
}
|
|
440
|
+
const intersectionIsAllowedJunction = allowedJunctionPoints.some(
|
|
441
|
+
(junctionPoint) =>
|
|
442
|
+
(pointsAreEqual(pathStart, junctionPoint) ||
|
|
443
|
+
pointsAreEqual(pathEnd, junctionPoint)) &&
|
|
444
|
+
(pointsAreEqual(traceStart, junctionPoint) ||
|
|
445
|
+
pointsAreEqual(traceEnd, junctionPoint)),
|
|
446
|
+
)
|
|
447
|
+
if (
|
|
448
|
+
intersectionIsAllowedJunction &&
|
|
449
|
+
!segmentsOverlapBeyondEndpoint({
|
|
450
|
+
firstStart: pathStart,
|
|
451
|
+
firstEnd: pathEnd,
|
|
452
|
+
secondStart: traceStart,
|
|
453
|
+
secondEnd: traceEnd,
|
|
454
|
+
})
|
|
455
|
+
) {
|
|
456
|
+
continue
|
|
457
|
+
}
|
|
458
|
+
return true
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export class UnroutedTraceRecoverySolver extends BaseSolver {
|
|
466
|
+
private inputProblem: InputProblem
|
|
467
|
+
private alreadySolvedTraces: SolvedTracePath[]
|
|
468
|
+
private failedConnectionPairs: MspConnectionPair[]
|
|
469
|
+
private queuedConnectionPairs: MspConnectionPair[]
|
|
470
|
+
private maxConnectionDistance: number
|
|
471
|
+
private groundGlobalConnNetId?: string
|
|
472
|
+
public solvedUnroutedTraces: SolvedTracePath[] = []
|
|
473
|
+
|
|
474
|
+
constructor(
|
|
475
|
+
private params: {
|
|
476
|
+
inputProblem: InputProblem
|
|
477
|
+
failedConnectionPairs: MspConnectionPair[]
|
|
478
|
+
alreadySolvedTraces: SolvedTracePath[]
|
|
479
|
+
},
|
|
480
|
+
) {
|
|
481
|
+
super()
|
|
482
|
+
this.inputProblem = params.inputProblem
|
|
483
|
+
this.alreadySolvedTraces = params.alreadySolvedTraces
|
|
484
|
+
this.failedConnectionPairs = params.failedConnectionPairs
|
|
485
|
+
this.queuedConnectionPairs = [...params.failedConnectionPairs]
|
|
486
|
+
this.maxConnectionDistance =
|
|
487
|
+
this.inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE
|
|
488
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
489
|
+
this.inputProblem,
|
|
490
|
+
)
|
|
491
|
+
this.groundGlobalConnNetId =
|
|
492
|
+
netConnMap.getNetConnectedToId(GROUND_NET_ID) ?? undefined
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
override getConstructorParams() {
|
|
496
|
+
return this.params
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
override _step() {
|
|
500
|
+
const connectionPair = this.queuedConnectionPairs.shift()
|
|
501
|
+
if (!connectionPair) {
|
|
502
|
+
this.solved = true
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
if (connectionPair.globalConnNetId === this.groundGlobalConnNetId) {
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
if (
|
|
509
|
+
distance(connectionPair.pins[0], connectionPair.pins[1]) >
|
|
510
|
+
this.maxConnectionDistance
|
|
511
|
+
) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const obstacles = getObstacleRects(this.inputProblem)
|
|
516
|
+
const existingTraces = [
|
|
517
|
+
...this.alreadySolvedTraces,
|
|
518
|
+
...this.solvedUnroutedTraces,
|
|
519
|
+
]
|
|
520
|
+
const sameNetTraces = existingTraces.filter(
|
|
521
|
+
(trace) => trace.globalConnNetId === connectionPair.globalConnNetId,
|
|
522
|
+
)
|
|
523
|
+
const junctionCandidates = getJunctionCandidates({
|
|
524
|
+
connectionPair,
|
|
525
|
+
sameNetTraces,
|
|
526
|
+
obstacles,
|
|
527
|
+
maxConnectionDistance: this.maxConnectionDistance,
|
|
528
|
+
})
|
|
529
|
+
const perimeterCandidates = getPerimeterCandidates({
|
|
530
|
+
connectionPair,
|
|
531
|
+
obstacles,
|
|
532
|
+
})
|
|
533
|
+
const candidates = [...junctionCandidates, ...perimeterCandidates]
|
|
534
|
+
const rejectComponentBoundaryTravel = hasParallelFailedConnection({
|
|
535
|
+
connectionPair,
|
|
536
|
+
failedConnectionPairs: this.failedConnectionPairs,
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
for (const tracePath of candidates) {
|
|
540
|
+
if (
|
|
541
|
+
pathCollidesWithObstacles({
|
|
542
|
+
path: tracePath,
|
|
543
|
+
obstacles,
|
|
544
|
+
connectionPair,
|
|
545
|
+
rejectComponentBoundaryTravel,
|
|
546
|
+
})
|
|
547
|
+
) {
|
|
548
|
+
continue
|
|
549
|
+
}
|
|
550
|
+
if (
|
|
551
|
+
pathCrossesExistingTraces({
|
|
552
|
+
path: tracePath,
|
|
553
|
+
connectionPair,
|
|
554
|
+
existingTraces,
|
|
555
|
+
})
|
|
556
|
+
) {
|
|
557
|
+
continue
|
|
558
|
+
}
|
|
559
|
+
this.solvedUnroutedTraces.push({
|
|
560
|
+
...connectionPair,
|
|
561
|
+
tracePath,
|
|
562
|
+
mspConnectionPairIds: [connectionPair.mspPairId],
|
|
563
|
+
pinIds: connectionPair.pins.map((pin) => pin.pinId),
|
|
564
|
+
})
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
getOutput(): {
|
|
570
|
+
newTraces: SolvedTracePath[]
|
|
571
|
+
allTracesMerged: SolvedTracePath[]
|
|
572
|
+
} {
|
|
573
|
+
return {
|
|
574
|
+
newTraces: this.solvedUnroutedTraces,
|
|
575
|
+
allTracesMerged: [
|
|
576
|
+
...this.alreadySolvedTraces,
|
|
577
|
+
...this.solvedUnroutedTraces,
|
|
578
|
+
],
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
override visualize() {
|
|
583
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
584
|
+
for (const trace of this.solvedUnroutedTraces) {
|
|
585
|
+
graphics.lines!.push({
|
|
586
|
+
points: trace.tracePath,
|
|
587
|
+
strokeColor: "blue",
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
return graphics
|
|
591
|
+
}
|
|
592
|
+
}
|
package/package.json
CHANGED