@tscircuit/schematic-trace-solver 0.0.140 → 0.0.141
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 +4 -0
- package/dist/index.js +43 -4
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +60 -4
- package/package.json +1 -1
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +542 -0
- package/tests/repros/assets/board-1273-trace-overlap-cycle.input.json +3519 -0
- package/tests/repros/board-1273-trace-overlap-cycle.test.ts +30 -0
package/dist/index.d.ts
CHANGED
|
@@ -279,6 +279,7 @@ declare class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
type ConnNetId = string;
|
|
282
|
+
type TraceState = Record<MspConnectionPairId, SolvedTracePath>;
|
|
282
283
|
/**
|
|
283
284
|
* This solver finds traces that overlap or meet collinearly and aren't
|
|
284
285
|
* connected via the globalConnMap, then shifts them apart in the direction
|
|
@@ -308,6 +309,7 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
308
309
|
*/
|
|
309
310
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>;
|
|
310
311
|
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath>;
|
|
312
|
+
recentTraceStates: TraceState[];
|
|
311
313
|
cleanupPhase: "diagonals" | "done" | null;
|
|
312
314
|
constructor(params: {
|
|
313
315
|
inputProblem: InputProblem;
|
|
@@ -324,6 +326,8 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
324
326
|
private findNextDiagonalSegment;
|
|
325
327
|
private findAndFixNextDiagonalSegment;
|
|
326
328
|
_step(): void;
|
|
329
|
+
private rememberTraceState;
|
|
330
|
+
private returnsToPreviousTraceState;
|
|
327
331
|
visualize(): graphics_debug.GraphicsObject;
|
|
328
332
|
}
|
|
329
333
|
|
package/dist/index.js
CHANGED
|
@@ -2102,6 +2102,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
2102
2102
|
};
|
|
2103
2103
|
|
|
2104
2104
|
// lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts
|
|
2105
|
+
var TRACE_STATE_POSITION_EPSILON = 1e-6;
|
|
2105
2106
|
var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
2106
2107
|
inputProblem;
|
|
2107
2108
|
inputTracePaths;
|
|
@@ -2112,6 +2113,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2112
2113
|
*/
|
|
2113
2114
|
traceNetIslands = {};
|
|
2114
2115
|
correctedTraceMap = {};
|
|
2116
|
+
// Keep only the current and previous layouts to detect a two-state cycle
|
|
2117
|
+
// without accumulating routing history for the whole solve.
|
|
2118
|
+
recentTraceStates = [];
|
|
2115
2119
|
cleanupPhase = null;
|
|
2116
2120
|
constructor(params) {
|
|
2117
2121
|
super();
|
|
@@ -2123,6 +2127,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2123
2127
|
const { mspPairId } = tracePath;
|
|
2124
2128
|
this.correctedTraceMap[mspPairId] = tracePath;
|
|
2125
2129
|
}
|
|
2130
|
+
this.rememberTraceState(this.correctedTraceMap);
|
|
2126
2131
|
this.traceNetIslands = this.computeTraceNetIslands();
|
|
2127
2132
|
}
|
|
2128
2133
|
getConstructorParams() {
|
|
@@ -2337,11 +2342,17 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2337
2342
|
}
|
|
2338
2343
|
_step() {
|
|
2339
2344
|
if (this.activeSubSolver?.solved) {
|
|
2340
|
-
|
|
2341
|
-
this.
|
|
2342
|
-
|
|
2343
|
-
|
|
2345
|
+
const nextTraceState = {
|
|
2346
|
+
...this.correctedTraceMap,
|
|
2347
|
+
...this.activeSubSolver.correctedTraceMap
|
|
2348
|
+
};
|
|
2349
|
+
if (this.returnsToPreviousTraceState(nextTraceState)) {
|
|
2350
|
+
this.activeSubSolver = null;
|
|
2351
|
+
this.solved = true;
|
|
2352
|
+
return;
|
|
2344
2353
|
}
|
|
2354
|
+
this.correctedTraceMap = nextTraceState;
|
|
2355
|
+
this.rememberTraceState(nextTraceState);
|
|
2345
2356
|
this.activeSubSolver = null;
|
|
2346
2357
|
this.traceNetIslands = this.computeTraceNetIslands();
|
|
2347
2358
|
}
|
|
@@ -2374,6 +2385,34 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2374
2385
|
traceIdsToShift: this.traceIdsToShift
|
|
2375
2386
|
});
|
|
2376
2387
|
}
|
|
2388
|
+
rememberTraceState(traceState) {
|
|
2389
|
+
this.recentTraceStates.push({ ...traceState });
|
|
2390
|
+
if (this.recentTraceStates.length > 2) {
|
|
2391
|
+
this.recentTraceStates.shift();
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
returnsToPreviousTraceState(candidateTraceState) {
|
|
2395
|
+
if (this.recentTraceStates.length < 2) return false;
|
|
2396
|
+
const previousTraceState = this.recentTraceStates[0];
|
|
2397
|
+
const traceIds = Object.keys(candidateTraceState);
|
|
2398
|
+
if (traceIds.length !== Object.keys(previousTraceState).length) return false;
|
|
2399
|
+
for (const traceId of traceIds) {
|
|
2400
|
+
const candidatePath = candidateTraceState[traceId];
|
|
2401
|
+
const previousPath = previousTraceState[traceId];
|
|
2402
|
+
if (!candidatePath || !previousPath) return false;
|
|
2403
|
+
if (candidatePath.tracePath.length !== previousPath.tracePath.length) {
|
|
2404
|
+
return false;
|
|
2405
|
+
}
|
|
2406
|
+
for (let i = 0; i < candidatePath.tracePath.length; i++) {
|
|
2407
|
+
const candidatePoint = candidatePath.tracePath[i];
|
|
2408
|
+
const previousPoint = previousPath.tracePath[i];
|
|
2409
|
+
if (Math.abs(candidatePoint.x - previousPoint.x) > TRACE_STATE_POSITION_EPSILON || Math.abs(candidatePoint.y - previousPoint.y) > TRACE_STATE_POSITION_EPSILON) {
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return true;
|
|
2415
|
+
}
|
|
2377
2416
|
visualize() {
|
|
2378
2417
|
if (this.activeSubSolver) {
|
|
2379
2418
|
return this.activeSubSolver.visualize();
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
import type { MspConnectionPairId } from "../MspConnectionPairSolver/MspConnectionPairSolver"
|
|
12
12
|
|
|
13
13
|
type ConnNetId = string
|
|
14
|
+
type TraceState = Record<MspConnectionPairId, SolvedTracePath>
|
|
15
|
+
|
|
16
|
+
const TRACE_STATE_POSITION_EPSILON = 1e-6
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* This solver finds traces that overlap or meet collinearly and aren't
|
|
@@ -44,6 +47,9 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
44
47
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>> = {}
|
|
45
48
|
|
|
46
49
|
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
|
|
50
|
+
// Keep only the current and previous layouts to detect a two-state cycle
|
|
51
|
+
// without accumulating routing history for the whole solve.
|
|
52
|
+
recentTraceStates: TraceState[] = []
|
|
47
53
|
|
|
48
54
|
cleanupPhase: "diagonals" | "done" | null = null
|
|
49
55
|
|
|
@@ -64,6 +70,8 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
64
70
|
this.correctedTraceMap[mspPairId] = tracePath
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
this.rememberTraceState(this.correctedTraceMap)
|
|
74
|
+
|
|
67
75
|
this.traceNetIslands = this.computeTraceNetIslands()
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -361,11 +369,19 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
361
369
|
|
|
362
370
|
override _step() {
|
|
363
371
|
if (this.activeSubSolver?.solved) {
|
|
364
|
-
|
|
365
|
-
this.
|
|
366
|
-
|
|
367
|
-
this.correctedTraceMap[mspPairId] = newTrace
|
|
372
|
+
const nextTraceState = {
|
|
373
|
+
...this.correctedTraceMap,
|
|
374
|
+
...this.activeSubSolver.correctedTraceMap,
|
|
368
375
|
}
|
|
376
|
+
// Returning to the older retained layout means corrections are bouncing
|
|
377
|
+
// A -> B -> A. Keep B and finish this pass instead of retrying forever.
|
|
378
|
+
if (this.returnsToPreviousTraceState(nextTraceState)) {
|
|
379
|
+
this.activeSubSolver = null
|
|
380
|
+
this.solved = true
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
this.correctedTraceMap = nextTraceState
|
|
384
|
+
this.rememberTraceState(nextTraceState)
|
|
369
385
|
this.activeSubSolver = null
|
|
370
386
|
this.traceNetIslands = this.computeTraceNetIslands()
|
|
371
387
|
}
|
|
@@ -407,6 +423,46 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
407
423
|
})
|
|
408
424
|
}
|
|
409
425
|
|
|
426
|
+
private rememberTraceState(traceState: TraceState) {
|
|
427
|
+
this.recentTraceStates.push({ ...traceState })
|
|
428
|
+
if (this.recentTraceStates.length > 2) {
|
|
429
|
+
this.recentTraceStates.shift()
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private returnsToPreviousTraceState(candidateTraceState: TraceState) {
|
|
434
|
+
if (this.recentTraceStates.length < 2) return false
|
|
435
|
+
|
|
436
|
+
const previousTraceState = this.recentTraceStates[0]!
|
|
437
|
+
const traceIds = Object.keys(candidateTraceState)
|
|
438
|
+
if (traceIds.length !== Object.keys(previousTraceState).length) return false
|
|
439
|
+
|
|
440
|
+
// Corrections create new objects, so compare the trace geometry itself.
|
|
441
|
+
for (const traceId of traceIds) {
|
|
442
|
+
const candidatePath = candidateTraceState[traceId]
|
|
443
|
+
const previousPath = previousTraceState[traceId]
|
|
444
|
+
if (!candidatePath || !previousPath) return false
|
|
445
|
+
if (candidatePath.tracePath.length !== previousPath.tracePath.length) {
|
|
446
|
+
return false
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < candidatePath.tracePath.length; i++) {
|
|
450
|
+
const candidatePoint = candidatePath.tracePath[i]!
|
|
451
|
+
const previousPoint = previousPath.tracePath[i]!
|
|
452
|
+
if (
|
|
453
|
+
Math.abs(candidatePoint.x - previousPoint.x) >
|
|
454
|
+
TRACE_STATE_POSITION_EPSILON ||
|
|
455
|
+
Math.abs(candidatePoint.y - previousPoint.y) >
|
|
456
|
+
TRACE_STATE_POSITION_EPSILON
|
|
457
|
+
) {
|
|
458
|
+
return false
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
465
|
+
|
|
410
466
|
override visualize() {
|
|
411
467
|
if (this.activeSubSolver) {
|
|
412
468
|
return this.activeSubSolver.visualize()
|