@tscircuit/schematic-trace-solver 0.0.65 → 0.0.66
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 +6 -0
- package/dist/index.js +94 -7
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +8 -2
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +6 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +6 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +68 -1
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +21 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/applyJogToTrace.ts +30 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example27.snap.svg +86 -78
- package/tests/examples/__snapshots__/example37.snap.svg +60 -156
- package/tests/examples/__snapshots__/example40.snap.svg +103 -63
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +460 -0
- package/tests/repros/assets/repro130-bq27441-fuel-gauge.input.json +350 -0
- package/tests/repros/repro130-bq27441-fuel-gauge-trace-through-c1.test.ts +60 -0
package/dist/index.d.ts
CHANGED
|
@@ -384,6 +384,7 @@ declare class NetLabelPlacementSolver extends BaseSolver {
|
|
|
384
384
|
queuedOverlappingSameNetTraceGroups: Array<OverlappingSameNetTraceGroup>;
|
|
385
385
|
activeSubSolver: SingleNetLabelPlacementSolver | null;
|
|
386
386
|
netLabelPlacements: Array<NetLabelPlacement>;
|
|
387
|
+
failedGroups: Array<OverlappingSameNetTraceGroup>;
|
|
387
388
|
currentGroup: OverlappingSameNetTraceGroup | null;
|
|
388
389
|
triedAnyOrientationFallbackForCurrentGroup: boolean;
|
|
389
390
|
constructor(params: {
|
|
@@ -434,6 +435,7 @@ interface SingleOverlapSolverInput {
|
|
|
434
435
|
problem: InputProblem;
|
|
435
436
|
paddingBuffer: number;
|
|
436
437
|
detourCount: number;
|
|
438
|
+
tracesToAvoidOverlapping?: SolvedTracePath[];
|
|
437
439
|
}
|
|
438
440
|
/**
|
|
439
441
|
* This solver attempts to find a valid rerouting for a single trace that is
|
|
@@ -447,6 +449,7 @@ declare class SingleOverlapSolver extends BaseSolver {
|
|
|
447
449
|
problem: InputProblem;
|
|
448
450
|
obstacles: ReturnType<typeof getObstacleRects>;
|
|
449
451
|
label: NetLabelPlacement;
|
|
452
|
+
tracesToAvoidOverlapping: SolvedTracePath[];
|
|
450
453
|
_tried: number;
|
|
451
454
|
constructor(solverInput: SingleOverlapSolverInput);
|
|
452
455
|
_step(): void;
|
|
@@ -460,6 +463,7 @@ interface OverlapCollectionSolverInput {
|
|
|
460
463
|
mergedNetLabelPlacements: NetLabelPlacement[];
|
|
461
464
|
mergedLabelNetIdMap: Record<string, Set<string>>;
|
|
462
465
|
detourCounts: Map<string, number>;
|
|
466
|
+
tracesToAvoidOverlapping?: SolvedTracePath[];
|
|
463
467
|
}
|
|
464
468
|
/**
|
|
465
469
|
* This is an internal solver that manages the step-by-step process of avoiding
|
|
@@ -471,6 +475,7 @@ declare class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
471
475
|
mergedNetLabelPlacements: NetLabelPlacement[];
|
|
472
476
|
mergedLabelNetIdMap: Record<string, Set<string>>;
|
|
473
477
|
allTraces: SolvedTracePath[];
|
|
478
|
+
tracesToAvoidOverlapping: SolvedTracePath[];
|
|
474
479
|
modifiedTraces: SolvedTracePath[];
|
|
475
480
|
private readonly PADDING_BUFFER;
|
|
476
481
|
private detourCounts;
|
|
@@ -517,6 +522,7 @@ declare class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
517
522
|
private phase;
|
|
518
523
|
detourCounts: Map<string, number>;
|
|
519
524
|
labelMergingSolver?: MergedNetLabelObstacleSolver;
|
|
525
|
+
allInputTraces: SolvedTracePath[];
|
|
520
526
|
constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput);
|
|
521
527
|
_step(): void;
|
|
522
528
|
getOutput(): {
|
package/dist/index.js
CHANGED
|
@@ -1059,6 +1059,32 @@ var applyJogToTerminalSegment = ({
|
|
|
1059
1059
|
const isHorizontal3 = Math.abs(start.y - end.y) < EPS11;
|
|
1060
1060
|
if (!isVertical4 && !isHorizontal3) return;
|
|
1061
1061
|
const segDir = isVertical4 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
|
|
1062
|
+
if (pts.length === 2) {
|
|
1063
|
+
if (isVertical4) {
|
|
1064
|
+
const jogYNearStart = start.y + segDir * JOG_SIZE;
|
|
1065
|
+
const jogYNearEnd = end.y - segDir * JOG_SIZE;
|
|
1066
|
+
pts.splice(
|
|
1067
|
+
1,
|
|
1068
|
+
0,
|
|
1069
|
+
{ x: start.x, y: jogYNearStart },
|
|
1070
|
+
{ x: start.x + offset, y: jogYNearStart },
|
|
1071
|
+
{ x: end.x + offset, y: jogYNearEnd },
|
|
1072
|
+
{ x: end.x, y: jogYNearEnd }
|
|
1073
|
+
);
|
|
1074
|
+
} else {
|
|
1075
|
+
const jogXNearStart = start.x + segDir * JOG_SIZE;
|
|
1076
|
+
const jogXNearEnd = end.x - segDir * JOG_SIZE;
|
|
1077
|
+
pts.splice(
|
|
1078
|
+
1,
|
|
1079
|
+
0,
|
|
1080
|
+
{ x: jogXNearStart, y: start.y },
|
|
1081
|
+
{ x: jogXNearStart, y: start.y + offset },
|
|
1082
|
+
{ x: jogXNearEnd, y: end.y + offset },
|
|
1083
|
+
{ x: jogXNearEnd, y: end.y }
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1062
1088
|
if (si === 0) {
|
|
1063
1089
|
if (isVertical4) {
|
|
1064
1090
|
const jogY = start.y + segDir * JOG_SIZE;
|
|
@@ -1126,7 +1152,18 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
1126
1152
|
}
|
|
1127
1153
|
}
|
|
1128
1154
|
_step() {
|
|
1155
|
+
const containsStraightPinToPinTrace = (group) => group.pathsWithOverlap.some(({ solvedTracePathIndex }) => {
|
|
1156
|
+
const path = this.traceNetIslands[group.connNetId][solvedTracePathIndex];
|
|
1157
|
+
return path.tracePath.length === 2;
|
|
1158
|
+
});
|
|
1159
|
+
const groupShouldStayInPlace = this.overlappingTraceSegments.map(
|
|
1160
|
+
containsStraightPinToPinTrace
|
|
1161
|
+
);
|
|
1162
|
+
const someGroupCanShift = groupShouldStayInPlace.some(
|
|
1163
|
+
(shouldStay) => !shouldStay
|
|
1164
|
+
);
|
|
1129
1165
|
const offsets = this.overlappingTraceSegments.map((group, idx) => {
|
|
1166
|
+
if (someGroupCanShift && groupShouldStayInPlace[idx]) return 0;
|
|
1130
1167
|
const n = Math.floor(idx / 2) + 1;
|
|
1131
1168
|
const signed = idx % 2 === 0 ? -n : n;
|
|
1132
1169
|
return this.getObstacleAwareOffset({
|
|
@@ -1138,6 +1175,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
1138
1175
|
const samePoint = (p, q) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y);
|
|
1139
1176
|
this.overlappingTraceSegments.forEach((group, gidx) => {
|
|
1140
1177
|
const offset = offsets[gidx];
|
|
1178
|
+
if (offset === 0) return;
|
|
1141
1179
|
const byPath = /* @__PURE__ */ new Map();
|
|
1142
1180
|
for (const loc of group.pathsWithOverlap) {
|
|
1143
1181
|
if (!byPath.has(loc.solvedTracePathIndex)) {
|
|
@@ -2146,6 +2184,7 @@ var NetLabelPlacementSolver = class extends BaseSolver {
|
|
|
2146
2184
|
overlappingSameNetTraceGroups;
|
|
2147
2185
|
queuedOverlappingSameNetTraceGroups;
|
|
2148
2186
|
netLabelPlacements = [];
|
|
2187
|
+
failedGroups = [];
|
|
2149
2188
|
currentGroup = null;
|
|
2150
2189
|
triedAnyOrientationFallbackForCurrentGroup = false;
|
|
2151
2190
|
constructor(params) {
|
|
@@ -2351,8 +2390,12 @@ var NetLabelPlacementSolver = class extends BaseSolver {
|
|
|
2351
2390
|
});
|
|
2352
2391
|
return;
|
|
2353
2392
|
}
|
|
2354
|
-
this.
|
|
2355
|
-
|
|
2393
|
+
if (this.currentGroup) {
|
|
2394
|
+
this.failedGroups.push(this.currentGroup);
|
|
2395
|
+
}
|
|
2396
|
+
this.activeSubSolver = null;
|
|
2397
|
+
this.currentGroup = null;
|
|
2398
|
+
this.triedAnyOrientationFallbackForCurrentGroup = false;
|
|
2356
2399
|
return;
|
|
2357
2400
|
}
|
|
2358
2401
|
if (this.activeSubSolver) {
|
|
@@ -3013,6 +3056,38 @@ var generateRerouteCandidates = ({
|
|
|
3013
3056
|
|
|
3014
3057
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
|
|
3015
3058
|
var MAX_TRIES = 5;
|
|
3059
|
+
var COINCIDENT_EPS = 2e-3;
|
|
3060
|
+
var doesPathCoincideWithTraces = (path, traces) => {
|
|
3061
|
+
const rangesOverlap1D = (a1, a2, b1, b2) => Math.min(Math.max(a1, a2), Math.max(b1, b2)) - Math.max(Math.min(a1, a2), Math.min(b1, b2)) > COINCIDENT_EPS;
|
|
3062
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
3063
|
+
const pathSegStart = path[i];
|
|
3064
|
+
const pathSegEnd = path[i + 1];
|
|
3065
|
+
const isVertical4 = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS;
|
|
3066
|
+
const isHorizontal3 = Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS;
|
|
3067
|
+
if (!isVertical4 && !isHorizontal3) continue;
|
|
3068
|
+
const crossAxis = isVertical4 ? "x" : "y";
|
|
3069
|
+
const alongAxis = isVertical4 ? "y" : "x";
|
|
3070
|
+
for (const trace of traces) {
|
|
3071
|
+
for (let j = 0; j < trace.tracePath.length - 1; j++) {
|
|
3072
|
+
const traceSegStart = trace.tracePath[j];
|
|
3073
|
+
const traceSegEnd = trace.tracePath[j + 1];
|
|
3074
|
+
const isParallel = Math.abs(traceSegStart[crossAxis] - traceSegEnd[crossAxis]) < COINCIDENT_EPS;
|
|
3075
|
+
if (!isParallel) continue;
|
|
3076
|
+
const isCoincident = Math.abs(pathSegStart[crossAxis] - traceSegStart[crossAxis]) < COINCIDENT_EPS;
|
|
3077
|
+
if (!isCoincident) continue;
|
|
3078
|
+
if (rangesOverlap1D(
|
|
3079
|
+
pathSegStart[alongAxis],
|
|
3080
|
+
pathSegEnd[alongAxis],
|
|
3081
|
+
traceSegStart[alongAxis],
|
|
3082
|
+
traceSegEnd[alongAxis]
|
|
3083
|
+
)) {
|
|
3084
|
+
return true;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
return false;
|
|
3090
|
+
};
|
|
3016
3091
|
var SingleOverlapSolver = class extends BaseSolver {
|
|
3017
3092
|
queuedCandidatePaths;
|
|
3018
3093
|
solvedTracePath = null;
|
|
@@ -3020,12 +3095,14 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
3020
3095
|
problem;
|
|
3021
3096
|
obstacles;
|
|
3022
3097
|
label;
|
|
3098
|
+
tracesToAvoidOverlapping;
|
|
3023
3099
|
_tried = 0;
|
|
3024
3100
|
constructor(solverInput) {
|
|
3025
3101
|
super();
|
|
3026
3102
|
this.initialTrace = solverInput.trace;
|
|
3027
3103
|
this.problem = solverInput.problem;
|
|
3028
3104
|
this.label = solverInput.label;
|
|
3105
|
+
this.tracesToAvoidOverlapping = (solverInput.tracesToAvoidOverlapping ?? []).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId);
|
|
3029
3106
|
const effectivePadding = solverInput.paddingBuffer + solverInput.detourCount * solverInput.paddingBuffer;
|
|
3030
3107
|
const candidates = generateRerouteCandidates({
|
|
3031
3108
|
...solverInput,
|
|
@@ -3054,7 +3131,7 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
3054
3131
|
this._tried++;
|
|
3055
3132
|
const nextCandidatePath = this.queuedCandidatePaths.shift();
|
|
3056
3133
|
const simplifiedPath = simplifyPath(nextCandidatePath);
|
|
3057
|
-
if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
|
|
3134
|
+
if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
|
|
3058
3135
|
this.solvedTracePath = simplifiedPath;
|
|
3059
3136
|
this.solved = true;
|
|
3060
3137
|
}
|
|
@@ -3159,6 +3236,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
3159
3236
|
mergedNetLabelPlacements;
|
|
3160
3237
|
mergedLabelNetIdMap;
|
|
3161
3238
|
allTraces;
|
|
3239
|
+
tracesToAvoidOverlapping;
|
|
3162
3240
|
modifiedTraces = [];
|
|
3163
3241
|
PADDING_BUFFER = 0.1;
|
|
3164
3242
|
detourCounts = /* @__PURE__ */ new Map();
|
|
@@ -3174,6 +3252,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
3174
3252
|
this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements;
|
|
3175
3253
|
this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
|
|
3176
3254
|
this.allTraces = [...solverInput.traces];
|
|
3255
|
+
this.tracesToAvoidOverlapping = solverInput.tracesToAvoidOverlapping ?? [];
|
|
3177
3256
|
this.detourCounts = solverInput.detourCounts;
|
|
3178
3257
|
}
|
|
3179
3258
|
_step() {
|
|
@@ -3255,7 +3334,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
3255
3334
|
label: actualOverlapLabel,
|
|
3256
3335
|
problem: this.inputProblem,
|
|
3257
3336
|
paddingBuffer: this.PADDING_BUFFER,
|
|
3258
|
-
detourCount: detourCount2
|
|
3337
|
+
detourCount: detourCount2,
|
|
3338
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
|
|
3259
3339
|
});
|
|
3260
3340
|
} else {
|
|
3261
3341
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
|
|
@@ -3290,7 +3370,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
3290
3370
|
label: actualOverlapLabel,
|
|
3291
3371
|
problem: this.inputProblem,
|
|
3292
3372
|
paddingBuffer: this.PADDING_BUFFER,
|
|
3293
|
-
detourCount: detourCount2
|
|
3373
|
+
detourCount: detourCount2,
|
|
3374
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
|
|
3294
3375
|
});
|
|
3295
3376
|
} else {
|
|
3296
3377
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
|
|
@@ -3305,7 +3386,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
3305
3386
|
label: labelToAvoid,
|
|
3306
3387
|
problem: this.inputProblem,
|
|
3307
3388
|
paddingBuffer: this.PADDING_BUFFER,
|
|
3308
|
-
detourCount
|
|
3389
|
+
detourCount,
|
|
3390
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
|
|
3309
3391
|
});
|
|
3310
3392
|
}
|
|
3311
3393
|
}
|
|
@@ -3373,10 +3455,12 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
|
|
|
3373
3455
|
phase = "searching_for_overlaps";
|
|
3374
3456
|
detourCounts = /* @__PURE__ */ new Map();
|
|
3375
3457
|
labelMergingSolver;
|
|
3458
|
+
allInputTraces;
|
|
3376
3459
|
constructor(solverInput) {
|
|
3377
3460
|
super();
|
|
3378
3461
|
this.inputProblem = solverInput.inputProblem;
|
|
3379
3462
|
this.unprocessedTraces = [...solverInput.traces];
|
|
3463
|
+
this.allInputTraces = solverInput.traces;
|
|
3380
3464
|
this.netLabelPlacements = solverInput.netLabelPlacements;
|
|
3381
3465
|
this.cleanTraces = [];
|
|
3382
3466
|
this.subSolvers = [];
|
|
@@ -3412,7 +3496,10 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
|
|
|
3412
3496
|
initialNetLabelPlacements: this.netLabelPlacements,
|
|
3413
3497
|
mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
|
|
3414
3498
|
mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
|
|
3415
|
-
detourCounts: this.detourCounts
|
|
3499
|
+
detourCounts: this.detourCounts,
|
|
3500
|
+
tracesToAvoidOverlapping: this.allInputTraces.filter(
|
|
3501
|
+
(t) => t.mspPairId !== currentTargetTrace.mspPairId
|
|
3502
|
+
)
|
|
3416
3503
|
});
|
|
3417
3504
|
this.subSolvers.push(subSolver);
|
|
3418
3505
|
}
|
|
@@ -74,6 +74,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
74
74
|
declare activeSubSolver: SingleNetLabelPlacementSolver | null
|
|
75
75
|
|
|
76
76
|
netLabelPlacements: Array<NetLabelPlacement> = []
|
|
77
|
+
failedGroups: Array<OverlappingSameNetTraceGroup> = []
|
|
77
78
|
currentGroup: OverlappingSameNetTraceGroup | null = null
|
|
78
79
|
triedAnyOrientationFallbackForCurrentGroup = false
|
|
79
80
|
|
|
@@ -336,8 +337,13 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
336
337
|
return
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
this
|
|
340
|
-
this.
|
|
340
|
+
// Record the failure for this group and continue to the next one
|
|
341
|
+
if (this.currentGroup) {
|
|
342
|
+
this.failedGroups.push(this.currentGroup)
|
|
343
|
+
}
|
|
344
|
+
this.activeSubSolver = null
|
|
345
|
+
this.currentGroup = null
|
|
346
|
+
this.triedAnyOrientationFallbackForCurrentGroup = false
|
|
341
347
|
return
|
|
342
348
|
}
|
|
343
349
|
|
|
@@ -42,10 +42,13 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
42
42
|
|
|
43
43
|
labelMergingSolver?: MergedNetLabelObstacleSolver
|
|
44
44
|
|
|
45
|
+
allInputTraces: SolvedTracePath[]
|
|
46
|
+
|
|
45
47
|
constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
|
|
46
48
|
super()
|
|
47
49
|
this.inputProblem = solverInput.inputProblem
|
|
48
50
|
this.unprocessedTraces = [...solverInput.traces]
|
|
51
|
+
this.allInputTraces = solverInput.traces
|
|
49
52
|
this.netLabelPlacements = solverInput.netLabelPlacements
|
|
50
53
|
this.cleanTraces = []
|
|
51
54
|
this.subSolvers = []
|
|
@@ -88,6 +91,9 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
88
91
|
mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
|
|
89
92
|
mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
|
|
90
93
|
detourCounts: this.detourCounts,
|
|
94
|
+
tracesToAvoidOverlapping: this.allInputTraces.filter(
|
|
95
|
+
(t) => t.mspPairId !== currentTargetTrace.mspPairId,
|
|
96
|
+
),
|
|
91
97
|
})
|
|
92
98
|
this.subSolvers.push(subSolver)
|
|
93
99
|
}
|
|
@@ -19,6 +19,7 @@ export interface OverlapCollectionSolverInput {
|
|
|
19
19
|
mergedNetLabelPlacements: NetLabelPlacement[]
|
|
20
20
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
21
21
|
detourCounts: Map<string, number>
|
|
22
|
+
tracesToAvoidOverlapping?: SolvedTracePath[]
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -32,6 +33,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
32
33
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
33
34
|
|
|
34
35
|
allTraces: SolvedTracePath[]
|
|
36
|
+
tracesToAvoidOverlapping: SolvedTracePath[]
|
|
35
37
|
modifiedTraces: SolvedTracePath[] = []
|
|
36
38
|
|
|
37
39
|
private readonly PADDING_BUFFER = 0.1
|
|
@@ -51,6 +53,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
51
53
|
this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
|
|
52
54
|
this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
|
|
53
55
|
this.allTraces = [...solverInput.traces]
|
|
56
|
+
this.tracesToAvoidOverlapping = solverInput.tracesToAvoidOverlapping ?? []
|
|
54
57
|
this.detourCounts = solverInput.detourCounts
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -150,6 +153,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
150
153
|
problem: this.inputProblem,
|
|
151
154
|
paddingBuffer: this.PADDING_BUFFER,
|
|
152
155
|
detourCount: detourCount,
|
|
156
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
153
157
|
})
|
|
154
158
|
} else {
|
|
155
159
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -192,6 +196,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
192
196
|
problem: this.inputProblem,
|
|
193
197
|
paddingBuffer: this.PADDING_BUFFER,
|
|
194
198
|
detourCount: detourCount,
|
|
199
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
195
200
|
})
|
|
196
201
|
} else {
|
|
197
202
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -211,6 +216,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
211
216
|
problem: this.inputProblem,
|
|
212
217
|
paddingBuffer: this.PADDING_BUFFER,
|
|
213
218
|
detourCount: detourCount,
|
|
219
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
214
220
|
})
|
|
215
221
|
}
|
|
216
222
|
}
|
|
@@ -16,9 +16,69 @@ interface SingleOverlapSolverInput {
|
|
|
16
16
|
problem: InputProblem
|
|
17
17
|
paddingBuffer: number
|
|
18
18
|
detourCount: number
|
|
19
|
+
tracesToAvoidOverlapping?: SolvedTracePath[]
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const MAX_TRIES = 5
|
|
23
|
+
const COINCIDENT_EPS = 2e-3
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if any segment of the path is parallel and coincident
|
|
27
|
+
* (within COINCIDENT_EPS) with a segment of one of the given traces.
|
|
28
|
+
* Perpendicular crossings are allowed.
|
|
29
|
+
*/
|
|
30
|
+
const doesPathCoincideWithTraces = (
|
|
31
|
+
path: Point[],
|
|
32
|
+
traces: SolvedTracePath[],
|
|
33
|
+
): boolean => {
|
|
34
|
+
const rangesOverlap1D = (a1: number, a2: number, b1: number, b2: number) =>
|
|
35
|
+
Math.min(Math.max(a1, a2), Math.max(b1, b2)) -
|
|
36
|
+
Math.max(Math.min(a1, a2), Math.min(b1, b2)) >
|
|
37
|
+
COINCIDENT_EPS
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
40
|
+
const pathSegStart = path[i]!
|
|
41
|
+
const pathSegEnd = path[i + 1]!
|
|
42
|
+
const isVertical = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS
|
|
43
|
+
const isHorizontal =
|
|
44
|
+
Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS
|
|
45
|
+
if (!isVertical && !isHorizontal) continue
|
|
46
|
+
|
|
47
|
+
// For a vertical segment, coincidence is measured in x and overlap in y
|
|
48
|
+
// (and vice versa for horizontal)
|
|
49
|
+
const crossAxis = isVertical ? "x" : "y"
|
|
50
|
+
const alongAxis = isVertical ? "y" : "x"
|
|
51
|
+
|
|
52
|
+
for (const trace of traces) {
|
|
53
|
+
for (let j = 0; j < trace.tracePath.length - 1; j++) {
|
|
54
|
+
const traceSegStart = trace.tracePath[j]!
|
|
55
|
+
const traceSegEnd = trace.tracePath[j + 1]!
|
|
56
|
+
|
|
57
|
+
const isParallel =
|
|
58
|
+
Math.abs(traceSegStart[crossAxis] - traceSegEnd[crossAxis]) <
|
|
59
|
+
COINCIDENT_EPS
|
|
60
|
+
if (!isParallel) continue
|
|
61
|
+
|
|
62
|
+
const isCoincident =
|
|
63
|
+
Math.abs(pathSegStart[crossAxis] - traceSegStart[crossAxis]) <
|
|
64
|
+
COINCIDENT_EPS
|
|
65
|
+
if (!isCoincident) continue
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
rangesOverlap1D(
|
|
69
|
+
pathSegStart[alongAxis],
|
|
70
|
+
pathSegEnd[alongAxis],
|
|
71
|
+
traceSegStart[alongAxis],
|
|
72
|
+
traceSegEnd[alongAxis],
|
|
73
|
+
)
|
|
74
|
+
) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
22
82
|
|
|
23
83
|
/**
|
|
24
84
|
* This solver attempts to find a valid rerouting for a single trace that is
|
|
@@ -32,6 +92,7 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
32
92
|
problem: InputProblem
|
|
33
93
|
obstacles: ReturnType<typeof getObstacleRects>
|
|
34
94
|
label: NetLabelPlacement
|
|
95
|
+
tracesToAvoidOverlapping: SolvedTracePath[]
|
|
35
96
|
_tried: number = 0
|
|
36
97
|
|
|
37
98
|
constructor(solverInput: SingleOverlapSolverInput) {
|
|
@@ -39,6 +100,9 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
39
100
|
this.initialTrace = solverInput.trace
|
|
40
101
|
this.problem = solverInput.problem
|
|
41
102
|
this.label = solverInput.label
|
|
103
|
+
this.tracesToAvoidOverlapping = (
|
|
104
|
+
solverInput.tracesToAvoidOverlapping ?? []
|
|
105
|
+
).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId)
|
|
42
106
|
|
|
43
107
|
// Calculate an effective padding for this specific run based on the detourCount.
|
|
44
108
|
const effectivePadding =
|
|
@@ -77,7 +141,10 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
77
141
|
const nextCandidatePath = this.queuedCandidatePaths.shift()!
|
|
78
142
|
const simplifiedPath = simplifyPath(nextCandidatePath)
|
|
79
143
|
|
|
80
|
-
if (
|
|
144
|
+
if (
|
|
145
|
+
!isPathCollidingWithObstacles(simplifiedPath, this.obstacles) &&
|
|
146
|
+
!doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)
|
|
147
|
+
) {
|
|
81
148
|
this.solvedTracePath = simplifiedPath
|
|
82
149
|
this.solved = true
|
|
83
150
|
}
|
package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
CHANGED
|
@@ -57,8 +57,28 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
57
57
|
// Shift only the overlapping segments, and move the shared endpoints
|
|
58
58
|
// (the last point of the previous segment and the first point of the next
|
|
59
59
|
// segment) so the polyline remains orthogonal without self-overlap.
|
|
60
|
+
// Groups containing a straight pin-to-pin trace (2 points, both endpoints
|
|
61
|
+
// are pins) keep their position when another group can shift instead:
|
|
62
|
+
// shifting such a trace would force jogs on an otherwise straight line.
|
|
63
|
+
const containsStraightPinToPinTrace = (
|
|
64
|
+
group: OverlappingTraceSegmentLocator,
|
|
65
|
+
) =>
|
|
66
|
+
group.pathsWithOverlap.some(({ solvedTracePathIndex }) => {
|
|
67
|
+
const path =
|
|
68
|
+
this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
|
|
69
|
+
return path.tracePath.length === 2
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const groupShouldStayInPlace = this.overlappingTraceSegments.map(
|
|
73
|
+
containsStraightPinToPinTrace,
|
|
74
|
+
)
|
|
75
|
+
const someGroupCanShift = groupShouldStayInPlace.some(
|
|
76
|
+
(shouldStay) => !shouldStay,
|
|
77
|
+
)
|
|
78
|
+
|
|
60
79
|
// Compute offsets for each island involved: alternate directions
|
|
61
80
|
const offsets = this.overlappingTraceSegments.map((group, idx) => {
|
|
81
|
+
if (someGroupCanShift && groupShouldStayInPlace[idx]) return 0
|
|
62
82
|
const n = Math.floor(idx / 2) + 1
|
|
63
83
|
const signed = idx % 2 === 0 ? -n : n
|
|
64
84
|
return this.getObstacleAwareOffset({
|
|
@@ -76,6 +96,7 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
76
96
|
// For each net island group, shift only its overlapping segments and adjust adjacent joints
|
|
77
97
|
this.overlappingTraceSegments.forEach((group, gidx) => {
|
|
78
98
|
const offset = offsets[gidx]!
|
|
99
|
+
if (offset === 0) return
|
|
79
100
|
|
|
80
101
|
// Gather unique segment indices per path
|
|
81
102
|
const byPath: Map<number, Set<number>> = new Map()
|
|
@@ -29,6 +29,36 @@ export const applyJogToTerminalSegment = ({
|
|
|
29
29
|
? 1
|
|
30
30
|
: -1
|
|
31
31
|
|
|
32
|
+
if (pts.length === 2) {
|
|
33
|
+
// The segment is both the first and last segment: both endpoints are
|
|
34
|
+
// pins, so jog inward on both sides to keep the endpoints anchored
|
|
35
|
+
if (isVertical) {
|
|
36
|
+
const jogYNearStart = start.y + segDir * JOG_SIZE
|
|
37
|
+
const jogYNearEnd = end.y - segDir * JOG_SIZE
|
|
38
|
+
pts.splice(
|
|
39
|
+
1,
|
|
40
|
+
0,
|
|
41
|
+
{ x: start.x, y: jogYNearStart },
|
|
42
|
+
{ x: start.x + offset, y: jogYNearStart },
|
|
43
|
+
{ x: end.x + offset, y: jogYNearEnd },
|
|
44
|
+
{ x: end.x, y: jogYNearEnd },
|
|
45
|
+
)
|
|
46
|
+
} else {
|
|
47
|
+
// Horizontal
|
|
48
|
+
const jogXNearStart = start.x + segDir * JOG_SIZE
|
|
49
|
+
const jogXNearEnd = end.x - segDir * JOG_SIZE
|
|
50
|
+
pts.splice(
|
|
51
|
+
1,
|
|
52
|
+
0,
|
|
53
|
+
{ x: jogXNearStart, y: start.y },
|
|
54
|
+
{ x: jogXNearStart, y: start.y + offset },
|
|
55
|
+
{ x: jogXNearEnd, y: end.y + offset },
|
|
56
|
+
{ x: jogXNearEnd, y: end.y },
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
if (si === 0) {
|
|
33
63
|
if (isVertical) {
|
|
34
64
|
const jogY = start.y + segDir * JOG_SIZE
|