@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.
Files changed (33) hide show
  1. package/dist/index.d.ts +31 -3
  2. package/dist/index.js +784 -247
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +3 -1
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +58 -27
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts +1 -10
  6. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateInternalSegmentCollisionDetours.ts +94 -0
  7. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +10 -0
  8. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts +5 -1
  9. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +18 -3
  10. package/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts +592 -0
  11. package/package.json +1 -1
  12. package/site/bug-reports/bug-report-20260724T175257Z.page.tsx +4 -0
  13. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +5 -9
  14. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +5 -5
  15. package/tests/bug-reports/bug-report-20260708T095725Z/bug-report-20260708T095725Z.test.ts +15 -0
  16. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +58 -0
  17. package/tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.json +138 -0
  18. package/tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.test.ts +12 -0
  19. package/tests/examples/__snapshots__/example04.snap.svg +7 -9
  20. package/tests/examples/__snapshots__/example08.snap.svg +16 -20
  21. package/tests/examples/__snapshots__/example13.snap.svg +2 -4
  22. package/tests/examples/__snapshots__/example32.snap.svg +64 -66
  23. package/tests/examples/__snapshots__/example46.snap.svg +1 -1
  24. package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +81 -0
  25. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +16 -18
  26. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +63 -220
  27. package/tests/repros/assets/repro-netlabel-collision-687.input.json +212 -0
  28. package/tests/repros/repro-netlabel-collision-687.test.ts +59 -0
  29. package/tests/repros/repro-tps61222-trace-intersection.test.ts +27 -0
  30. package/tests/solvers/SchematicTraceSingleLineSolver2/generate-internal-segment-collision-detours.test.ts +49 -0
  31. package/tests/solvers/UnroutedTraceRecoverySolver/paired-junction-recovery.test.ts +49 -0
  32. package/tests/solvers/UnroutedTraceRecoverySolver/skip-ground.test.ts +30 -0
  33. 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.107",
4
+ "version": "0.0.109",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />