@tscircuit/schematic-trace-solver 0.0.125 → 0.0.126
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 +45 -1
- package/dist/index.js +427 -35
- package/lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts +15 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +69 -11
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +216 -0
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts +186 -0
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/types.ts +10 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +3 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +16 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +56 -13
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260806T093501Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +6 -6
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +135 -0
- package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.json +505 -0
- package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.test.ts +66 -0
- package/tests/examples/__snapshots__/example33.snap.svg +1 -1
package/dist/index.js
CHANGED
|
@@ -4000,6 +4000,16 @@ var doesPathOverlapTraceStrokes = (path, traces) => {
|
|
|
4000
4000
|
|
|
4001
4001
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
|
|
4002
4002
|
var MAX_TRIES = 5;
|
|
4003
|
+
var PATH_LENGTH_EPSILON = 1e-9;
|
|
4004
|
+
var getPathLength = (points) => {
|
|
4005
|
+
let length = 0;
|
|
4006
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex++) {
|
|
4007
|
+
const point = points[pointIndex];
|
|
4008
|
+
const nextPoint = points[pointIndex + 1];
|
|
4009
|
+
length += Math.abs(nextPoint.x - point.x) + Math.abs(nextPoint.y - point.y);
|
|
4010
|
+
}
|
|
4011
|
+
return length;
|
|
4012
|
+
};
|
|
4003
4013
|
var SingleOverlapSolver = class extends BaseSolver {
|
|
4004
4014
|
queuedCandidatePaths;
|
|
4005
4015
|
solvedTracePath = null;
|
|
@@ -4008,32 +4018,47 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
4008
4018
|
obstacles;
|
|
4009
4019
|
label;
|
|
4010
4020
|
tracesToAvoidOverlapping;
|
|
4021
|
+
netLabelPlacements;
|
|
4022
|
+
detourCount;
|
|
4011
4023
|
_tried = 0;
|
|
4012
4024
|
constructor(solverInput) {
|
|
4013
4025
|
super();
|
|
4014
4026
|
this.initialTrace = solverInput.trace;
|
|
4015
4027
|
this.problem = solverInput.problem;
|
|
4016
4028
|
this.label = solverInput.label;
|
|
4029
|
+
this.detourCount = solverInput.detourCount;
|
|
4017
4030
|
this.tracesToAvoidOverlapping = (solverInput.tracesToAvoidOverlapping ?? []).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId);
|
|
4031
|
+
this.netLabelPlacements = solverInput.netLabelPlacements ?? [
|
|
4032
|
+
solverInput.label
|
|
4033
|
+
];
|
|
4034
|
+
this.obstacles = getObstacleRects(this.problem);
|
|
4018
4035
|
const effectivePadding = solverInput.paddingBuffer + solverInput.detourCount * solverInput.paddingBuffer;
|
|
4019
4036
|
const candidates = generateRerouteCandidates({
|
|
4020
4037
|
...solverInput,
|
|
4021
4038
|
paddingBuffer: effectivePadding
|
|
4022
4039
|
// Use the calculated, larger padding
|
|
4023
4040
|
});
|
|
4024
|
-
const
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
this.
|
|
4041
|
+
const getLabelOverlapCount = (path) => detectTraceLabelOverlap({
|
|
4042
|
+
traces: [{ ...this.initialTrace, tracePath: path }],
|
|
4043
|
+
netLabels: this.netLabelPlacements
|
|
4044
|
+
}).length;
|
|
4045
|
+
const candidateByPath = /* @__PURE__ */ new Map();
|
|
4046
|
+
for (const candidate of candidates) {
|
|
4047
|
+
const simplifiedCandidate = simplifyPath(candidate);
|
|
4048
|
+
candidateByPath.set(
|
|
4049
|
+
simplifiedCandidate.map((point) => `${point.x},${point.y}`).join(";"),
|
|
4050
|
+
simplifiedCandidate
|
|
4051
|
+
);
|
|
4052
|
+
}
|
|
4053
|
+
this.queuedCandidatePaths = [...candidateByPath.values()].sort((a, b) => {
|
|
4054
|
+
const pathLengthDifference = getPathLength(a) - getPathLength(b);
|
|
4055
|
+
if (Math.abs(pathLengthDifference) >= PATH_LENGTH_EPSILON) {
|
|
4056
|
+
return pathLengthDifference;
|
|
4057
|
+
}
|
|
4058
|
+
const overlapCountDifference = getLabelOverlapCount(a) - getLabelOverlapCount(b);
|
|
4059
|
+
if (overlapCountDifference !== 0) return overlapCountDifference;
|
|
4060
|
+
return a.length - b.length;
|
|
4061
|
+
});
|
|
4037
4062
|
}
|
|
4038
4063
|
_step() {
|
|
4039
4064
|
if (this.queuedCandidatePaths.length === 0 || this._tried >= MAX_TRIES) {
|
|
@@ -4047,7 +4072,16 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
4047
4072
|
traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
|
|
4048
4073
|
netLabels: [this.label]
|
|
4049
4074
|
}).length > 0;
|
|
4050
|
-
|
|
4075
|
+
const initialPath = simplifyPath(this.initialTrace.tracePath);
|
|
4076
|
+
const initialLabelOverlaps = detectTraceLabelOverlap({
|
|
4077
|
+
traces: [{ ...this.initialTrace, tracePath: initialPath }],
|
|
4078
|
+
netLabels: this.netLabelPlacements
|
|
4079
|
+
});
|
|
4080
|
+
const candidateLabelOverlaps = detectTraceLabelOverlap({
|
|
4081
|
+
traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
|
|
4082
|
+
netLabels: this.netLabelPlacements
|
|
4083
|
+
});
|
|
4084
|
+
if (!stillOverlapsLabel && candidateLabelOverlaps.length <= initialLabelOverlaps.length && !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
|
|
4051
4085
|
this.solvedTracePath = simplifiedPath;
|
|
4052
4086
|
this.solved = true;
|
|
4053
4087
|
}
|
|
@@ -4070,7 +4104,7 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
4070
4104
|
height: this.label.height,
|
|
4071
4105
|
fill: "rgba(255, 0, 0, 0.2)"
|
|
4072
4106
|
});
|
|
4073
|
-
if (this.queuedCandidatePaths.length > 0) {
|
|
4107
|
+
if (!this.solvedTracePath && this.queuedCandidatePaths.length > 0) {
|
|
4074
4108
|
graphics.lines.push({
|
|
4075
4109
|
points: this.queuedCandidatePaths[0],
|
|
4076
4110
|
strokeColor: "orange"
|
|
@@ -4154,6 +4188,7 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
4154
4188
|
allTraces;
|
|
4155
4189
|
tracesToAvoidOverlapping;
|
|
4156
4190
|
modifiedTraces = [];
|
|
4191
|
+
completedReroutes = [];
|
|
4157
4192
|
PADDING_BUFFER = 0.1;
|
|
4158
4193
|
detourCounts = /* @__PURE__ */ new Map();
|
|
4159
4194
|
activeSubSolver = null;
|
|
@@ -4179,6 +4214,17 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
4179
4214
|
if (this.activeSubSolver.solved) {
|
|
4180
4215
|
const solvedPath = this.activeSubSolver.solvedTracePath;
|
|
4181
4216
|
if (solvedPath) {
|
|
4217
|
+
this.completedReroutes.push({
|
|
4218
|
+
initialTrace: {
|
|
4219
|
+
...this.activeSubSolver.initialTrace,
|
|
4220
|
+
tracePath: this.activeSubSolver.initialTrace.tracePath.map(
|
|
4221
|
+
(point) => ({ ...point })
|
|
4222
|
+
)
|
|
4223
|
+
},
|
|
4224
|
+
reroutedTracePath: solvedPath.map((point) => ({ ...point })),
|
|
4225
|
+
label: this.activeSubSolver.label,
|
|
4226
|
+
detourCount: this.activeSubSolver.detourCount
|
|
4227
|
+
});
|
|
4182
4228
|
const traceIndex = this.allTraces.findIndex(
|
|
4183
4229
|
(t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
|
|
4184
4230
|
);
|
|
@@ -4251,7 +4297,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
4251
4297
|
problem: this.inputProblem,
|
|
4252
4298
|
paddingBuffer: this.PADDING_BUFFER,
|
|
4253
4299
|
detourCount: detourCount2,
|
|
4254
|
-
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
|
|
4300
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
4301
|
+
netLabelPlacements: this.initialNetLabelPlacements
|
|
4255
4302
|
});
|
|
4256
4303
|
} else {
|
|
4257
4304
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
|
|
@@ -4287,7 +4334,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
4287
4334
|
problem: this.inputProblem,
|
|
4288
4335
|
paddingBuffer: this.PADDING_BUFFER,
|
|
4289
4336
|
detourCount: detourCount2,
|
|
4290
|
-
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
|
|
4337
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
4338
|
+
netLabelPlacements: this.initialNetLabelPlacements
|
|
4291
4339
|
});
|
|
4292
4340
|
} else {
|
|
4293
4341
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
|
|
@@ -4303,7 +4351,8 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
|
|
|
4303
4351
|
problem: this.inputProblem,
|
|
4304
4352
|
paddingBuffer: this.PADDING_BUFFER,
|
|
4305
4353
|
detourCount,
|
|
4306
|
-
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping
|
|
4354
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
4355
|
+
netLabelPlacements: this.initialNetLabelPlacements
|
|
4307
4356
|
});
|
|
4308
4357
|
}
|
|
4309
4358
|
}
|
|
@@ -4440,6 +4489,9 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
|
|
|
4440
4489
|
const solvedTraces = this.subSolvers.flatMap((s) => s.getOutput().allTraces);
|
|
4441
4490
|
return {
|
|
4442
4491
|
traces: [...this.cleanTraces, ...solvedTraces],
|
|
4492
|
+
completedReroutes: this.subSolvers.flatMap(
|
|
4493
|
+
(solver) => solver.completedReroutes
|
|
4494
|
+
),
|
|
4443
4495
|
netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
|
|
4444
4496
|
};
|
|
4445
4497
|
}
|
|
@@ -5527,7 +5579,7 @@ var segmentRunsAlongRectBoundary = (start, end, rect) => {
|
|
|
5527
5579
|
|
|
5528
5580
|
// lib/solvers/Example28Solver/geometry.ts
|
|
5529
5581
|
var getPathKey = (path) => path.map((point) => `${point.x},${point.y}`).join(";");
|
|
5530
|
-
var
|
|
5582
|
+
var getPathLength2 = (path) => {
|
|
5531
5583
|
let length = 0;
|
|
5532
5584
|
for (let i = 0; i < path.length - 1; i++) {
|
|
5533
5585
|
length += Math.abs(path[i + 1].x - path[i].x) + Math.abs(path[i + 1].y - path[i].y);
|
|
@@ -5681,7 +5733,7 @@ var getTraceGeometryMetrics = (traces, allTraces) => ({
|
|
|
5681
5733
|
),
|
|
5682
5734
|
visibleLength: getVisibleTraceLength(traces),
|
|
5683
5735
|
pathLength: traces.reduce(
|
|
5684
|
-
(sum, trace) => sum +
|
|
5736
|
+
(sum, trace) => sum + getPathLength2(trace.tracePath),
|
|
5685
5737
|
0
|
|
5686
5738
|
),
|
|
5687
5739
|
otherNetCrossings: countOtherNetCrossings(traces, allTraces)
|
|
@@ -6496,7 +6548,7 @@ var UntangleTraceSubsolver = class extends BaseSolver {
|
|
|
6496
6548
|
candidate.traceId
|
|
6497
6549
|
).isColliding && (crossing.isInitialBundleCrossing || !candidate.collision.isColliding)
|
|
6498
6550
|
).sort(
|
|
6499
|
-
(first, second) => Number(first.collision.isColliding) - Number(second.collision.isColliding) ||
|
|
6551
|
+
(first, second) => Number(first.collision.isColliding) - Number(second.collision.isColliding) || getPathLength2(first.path) - getPathLength2(second.path)
|
|
6500
6552
|
);
|
|
6501
6553
|
const bestCandidate = validCandidates[0];
|
|
6502
6554
|
if (!bestCandidate) return false;
|
|
@@ -7405,7 +7457,7 @@ var scoreTracePath = ({
|
|
|
7405
7457
|
labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
|
|
7406
7458
|
traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
|
|
7407
7459
|
chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
|
|
7408
|
-
pathLength:
|
|
7460
|
+
pathLength: getPathLength2(tracePath)
|
|
7409
7461
|
};
|
|
7410
7462
|
};
|
|
7411
7463
|
var countTraceIntersections = (trace, outputTraces) => {
|
|
@@ -10297,6 +10349,7 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
|
|
|
10297
10349
|
netLabelPlacements;
|
|
10298
10350
|
outputTraces;
|
|
10299
10351
|
outputNetLabelPlacements;
|
|
10352
|
+
completedReroutes = [];
|
|
10300
10353
|
activeSubSolver = null;
|
|
10301
10354
|
recentlyFailed = /* @__PURE__ */ new Set();
|
|
10302
10355
|
detourCounts = /* @__PURE__ */ new Map();
|
|
@@ -10322,6 +10375,17 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
|
|
|
10322
10375
|
if (this.activeSubSolver.solved) {
|
|
10323
10376
|
const solvedPath = this.activeSubSolver.solvedTracePath;
|
|
10324
10377
|
if (solvedPath) {
|
|
10378
|
+
this.completedReroutes.push({
|
|
10379
|
+
initialTrace: {
|
|
10380
|
+
...this.activeSubSolver.initialTrace,
|
|
10381
|
+
tracePath: this.activeSubSolver.initialTrace.tracePath.map(
|
|
10382
|
+
(point) => ({ ...point })
|
|
10383
|
+
)
|
|
10384
|
+
},
|
|
10385
|
+
reroutedTracePath: solvedPath.map((point) => ({ ...point })),
|
|
10386
|
+
label: this.activeSubSolver.label,
|
|
10387
|
+
detourCount: this.activeSubSolver.detourCount
|
|
10388
|
+
});
|
|
10325
10389
|
const idx = this.outputTraces.findIndex(
|
|
10326
10390
|
(t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
|
|
10327
10391
|
);
|
|
@@ -10384,7 +10448,8 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
|
|
|
10384
10448
|
problem: this.inputProblem,
|
|
10385
10449
|
paddingBuffer: PADDING_BUFFER,
|
|
10386
10450
|
detourCount,
|
|
10387
|
-
tracesToAvoidOverlapping: this.outputTraces
|
|
10451
|
+
tracesToAvoidOverlapping: this.outputTraces,
|
|
10452
|
+
netLabelPlacements: this.outputNetLabelPlacements
|
|
10388
10453
|
});
|
|
10389
10454
|
}
|
|
10390
10455
|
/**
|
|
@@ -10416,6 +10481,7 @@ var NetLabelTraceCollisionSolver = class extends BaseSolver {
|
|
|
10416
10481
|
getOutput() {
|
|
10417
10482
|
return {
|
|
10418
10483
|
traces: this.outputTraces,
|
|
10484
|
+
completedReroutes: this.completedReroutes,
|
|
10419
10485
|
netLabelPlacements: this.outputNetLabelPlacements
|
|
10420
10486
|
};
|
|
10421
10487
|
}
|
|
@@ -10828,7 +10894,7 @@ var removeConsecutiveDuplicatePoints = (path) => {
|
|
|
10828
10894
|
}
|
|
10829
10895
|
return filteredPath;
|
|
10830
10896
|
};
|
|
10831
|
-
var
|
|
10897
|
+
var getPathLength3 = (path) => {
|
|
10832
10898
|
let pathLength = 0;
|
|
10833
10899
|
for (let pointIndex = 0; pointIndex < path.length - 1; pointIndex++) {
|
|
10834
10900
|
const startPoint = path[pointIndex];
|
|
@@ -10912,7 +10978,7 @@ var getPerimeterCandidates = ({
|
|
|
10912
10978
|
);
|
|
10913
10979
|
}
|
|
10914
10980
|
return candidates.sort(
|
|
10915
|
-
(firstPath, secondPath) =>
|
|
10981
|
+
(firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
|
|
10916
10982
|
);
|
|
10917
10983
|
};
|
|
10918
10984
|
var getSegmentMidpoint = (startPoint, endPoint) => {
|
|
@@ -11017,7 +11083,7 @@ var getJunctionCandidates = ({
|
|
|
11017
11083
|
}
|
|
11018
11084
|
}
|
|
11019
11085
|
return candidates.sort(
|
|
11020
|
-
(firstPath, secondPath) =>
|
|
11086
|
+
(firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
|
|
11021
11087
|
);
|
|
11022
11088
|
};
|
|
11023
11089
|
var pathCollidesWithObstacles = ({
|
|
@@ -11525,6 +11591,278 @@ orientation: ${label.orientation}`
|
|
|
11525
11591
|
}
|
|
11526
11592
|
};
|
|
11527
11593
|
|
|
11594
|
+
// lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts
|
|
11595
|
+
var isSimpleFiveSegmentElbow = (path) => {
|
|
11596
|
+
const simplifiedPath = simplifyPath(path);
|
|
11597
|
+
if (simplifiedPath.length !== 6) return false;
|
|
11598
|
+
const segmentIsHorizontal = simplifiedPath.slice(0, -1).map((point, index) => isHorizontal(point, simplifiedPath[index + 1]));
|
|
11599
|
+
return segmentIsHorizontal.every(
|
|
11600
|
+
(isHorizontalSegment, index) => index === 0 || isHorizontalSegment !== segmentIsHorizontal[index - 1]
|
|
11601
|
+
);
|
|
11602
|
+
};
|
|
11603
|
+
var generateSegmentShiftCandidates = ({
|
|
11604
|
+
trace,
|
|
11605
|
+
label,
|
|
11606
|
+
paddingBuffer,
|
|
11607
|
+
detourCount
|
|
11608
|
+
}) => {
|
|
11609
|
+
if (trace.globalConnNetId === label.globalConnNetId) return [];
|
|
11610
|
+
const path = simplifyPath(trace.tracePath);
|
|
11611
|
+
if (!isSimpleFiveSegmentElbow(path)) return [];
|
|
11612
|
+
const labelBounds = getRectBounds(label.center, label.width, label.height);
|
|
11613
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
|
|
11614
|
+
const paddedLabelBounds = {
|
|
11615
|
+
minX: labelBounds.minX - effectivePadding,
|
|
11616
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
11617
|
+
minY: labelBounds.minY - effectivePadding,
|
|
11618
|
+
maxY: labelBounds.maxY + effectivePadding
|
|
11619
|
+
};
|
|
11620
|
+
const candidates = [];
|
|
11621
|
+
for (let segmentIndex = 1; segmentIndex < path.length - 2; segmentIndex++) {
|
|
11622
|
+
const segmentStart = path[segmentIndex];
|
|
11623
|
+
const segmentEnd = path[segmentIndex + 1];
|
|
11624
|
+
if (!segmentIntersectsRect(segmentStart, segmentEnd, labelBounds)) continue;
|
|
11625
|
+
const isHorizontalSegment = isHorizontal(segmentStart, segmentEnd);
|
|
11626
|
+
const isVerticalSegment = isVertical(segmentStart, segmentEnd);
|
|
11627
|
+
if (!isHorizontalSegment && !isVerticalSegment) continue;
|
|
11628
|
+
const axis = isHorizontalSegment ? "y" : "x";
|
|
11629
|
+
const coordinates = isHorizontalSegment ? [paddedLabelBounds.minY, paddedLabelBounds.maxY] : [paddedLabelBounds.minX, paddedLabelBounds.maxX];
|
|
11630
|
+
for (const coordinate of coordinates) {
|
|
11631
|
+
const shiftedPath = shiftSegmentOrth(path, segmentIndex, axis, coordinate);
|
|
11632
|
+
if (shiftedPath) candidates.push(shiftedPath);
|
|
11633
|
+
}
|
|
11634
|
+
}
|
|
11635
|
+
return candidates;
|
|
11636
|
+
};
|
|
11637
|
+
var generateTransitionShiftCandidates = ({
|
|
11638
|
+
trace,
|
|
11639
|
+
label,
|
|
11640
|
+
paddingBuffer,
|
|
11641
|
+
detourCount
|
|
11642
|
+
}) => {
|
|
11643
|
+
const path = simplifyPath(trace.tracePath);
|
|
11644
|
+
if (!isSimpleFiveSegmentElbow(path)) return [];
|
|
11645
|
+
const labelBounds = getRectBounds(label.center, label.width, label.height);
|
|
11646
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
|
|
11647
|
+
const paddedLabelBounds = {
|
|
11648
|
+
minX: labelBounds.minX - effectivePadding,
|
|
11649
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
11650
|
+
minY: labelBounds.minY - effectivePadding,
|
|
11651
|
+
maxY: labelBounds.maxY + effectivePadding
|
|
11652
|
+
};
|
|
11653
|
+
const start = path[0];
|
|
11654
|
+
const end = path[path.length - 1];
|
|
11655
|
+
const middleSegmentIndex = 2;
|
|
11656
|
+
const candidates = [];
|
|
11657
|
+
for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) {
|
|
11658
|
+
const segmentStart = path[segmentIndex];
|
|
11659
|
+
const segmentEnd = path[segmentIndex + 1];
|
|
11660
|
+
if (!segmentIntersectsRect(segmentStart, segmentEnd, labelBounds)) continue;
|
|
11661
|
+
if (Math.abs(segmentIndex - middleSegmentIndex) !== 1) continue;
|
|
11662
|
+
const isHorizontalSegment = isHorizontal(segmentStart, segmentEnd);
|
|
11663
|
+
const isVerticalSegment = isVertical(segmentStart, segmentEnd);
|
|
11664
|
+
if (!isHorizontalSegment && !isVerticalSegment) continue;
|
|
11665
|
+
const axis = isHorizontalSegment ? "x" : "y";
|
|
11666
|
+
const min = isHorizontalSegment ? paddedLabelBounds.minX : paddedLabelBounds.minY;
|
|
11667
|
+
const max = isHorizontalSegment ? paddedLabelBounds.maxX : paddedLabelBounds.maxY;
|
|
11668
|
+
const startCoordinate = isHorizontalSegment ? start.x : start.y;
|
|
11669
|
+
const endCoordinate = isHorizontalSegment ? end.x : end.y;
|
|
11670
|
+
const corridorCoordinates = [];
|
|
11671
|
+
if (startCoordinate < min) {
|
|
11672
|
+
corridorCoordinates.push((startCoordinate + min) / 2);
|
|
11673
|
+
} else if (startCoordinate > max) {
|
|
11674
|
+
corridorCoordinates.push((startCoordinate + max) / 2);
|
|
11675
|
+
}
|
|
11676
|
+
if (endCoordinate < min) {
|
|
11677
|
+
corridorCoordinates.push((endCoordinate + min) / 2);
|
|
11678
|
+
} else if (endCoordinate > max) {
|
|
11679
|
+
corridorCoordinates.push((endCoordinate + max) / 2);
|
|
11680
|
+
}
|
|
11681
|
+
for (const coordinate of new Set(corridorCoordinates)) {
|
|
11682
|
+
const shiftedPath = shiftSegmentOrth(
|
|
11683
|
+
path,
|
|
11684
|
+
middleSegmentIndex,
|
|
11685
|
+
axis,
|
|
11686
|
+
coordinate
|
|
11687
|
+
);
|
|
11688
|
+
if (shiftedPath) candidates.push(shiftedPath);
|
|
11689
|
+
}
|
|
11690
|
+
}
|
|
11691
|
+
return candidates;
|
|
11692
|
+
};
|
|
11693
|
+
var generateElbowTransitionSimplificationCandidates = ({
|
|
11694
|
+
trace,
|
|
11695
|
+
label,
|
|
11696
|
+
netLabelPlacements,
|
|
11697
|
+
paddingBuffer,
|
|
11698
|
+
detourCount
|
|
11699
|
+
}) => {
|
|
11700
|
+
const transitionCandidates = generateTransitionShiftCandidates({
|
|
11701
|
+
trace,
|
|
11702
|
+
label,
|
|
11703
|
+
paddingBuffer,
|
|
11704
|
+
detourCount
|
|
11705
|
+
});
|
|
11706
|
+
return transitionCandidates.flatMap((tracePath) => {
|
|
11707
|
+
const shiftedTrace = { ...trace, tracePath };
|
|
11708
|
+
const shiftedOverlaps = detectTraceLabelOverlap({
|
|
11709
|
+
traces: [shiftedTrace],
|
|
11710
|
+
netLabels: netLabelPlacements
|
|
11711
|
+
});
|
|
11712
|
+
return shiftedOverlaps.flatMap(
|
|
11713
|
+
({ label: shiftedLabel }) => generateSegmentShiftCandidates({
|
|
11714
|
+
trace: shiftedTrace,
|
|
11715
|
+
label: shiftedLabel,
|
|
11716
|
+
paddingBuffer,
|
|
11717
|
+
detourCount
|
|
11718
|
+
})
|
|
11719
|
+
);
|
|
11720
|
+
});
|
|
11721
|
+
};
|
|
11722
|
+
|
|
11723
|
+
// lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts
|
|
11724
|
+
var PATH_LENGTH_EPSILON2 = 1e-9;
|
|
11725
|
+
var getPathLength4 = (points) => points.slice(1).reduce((length, point, pointIndex) => {
|
|
11726
|
+
const previousPoint = points[pointIndex];
|
|
11727
|
+
return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
|
|
11728
|
+
}, 0);
|
|
11729
|
+
var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
|
|
11730
|
+
input;
|
|
11731
|
+
outputTraces;
|
|
11732
|
+
traceIdQueue;
|
|
11733
|
+
obstacles;
|
|
11734
|
+
constructor(input) {
|
|
11735
|
+
super();
|
|
11736
|
+
this.input = input;
|
|
11737
|
+
this.outputTraces = [...input.traces];
|
|
11738
|
+
const reroutedTraceIds = new Set(
|
|
11739
|
+
input.completedReroutes.map(
|
|
11740
|
+
(completedReroute) => completedReroute.initialTrace.mspPairId
|
|
11741
|
+
)
|
|
11742
|
+
);
|
|
11743
|
+
this.traceIdQueue = input.traces.map((trace) => trace.mspPairId).filter((traceId) => reroutedTraceIds.has(traceId));
|
|
11744
|
+
this.obstacles = getObstacleRects(input.inputProblem);
|
|
11745
|
+
}
|
|
11746
|
+
_step() {
|
|
11747
|
+
const traceId = this.traceIdQueue.shift();
|
|
11748
|
+
if (!traceId) {
|
|
11749
|
+
this.solved = true;
|
|
11750
|
+
return;
|
|
11751
|
+
}
|
|
11752
|
+
const traceIndex = this.outputTraces.findIndex(
|
|
11753
|
+
(trace2) => trace2.mspPairId === traceId
|
|
11754
|
+
);
|
|
11755
|
+
const trace = this.outputTraces[traceIndex];
|
|
11756
|
+
const tracePath = simplifyPath(trace.tracePath);
|
|
11757
|
+
const initialOverlaps = detectTraceLabelOverlap({
|
|
11758
|
+
traces: [{ ...trace, tracePath }],
|
|
11759
|
+
netLabels: this.input.netLabelPlacements
|
|
11760
|
+
});
|
|
11761
|
+
const otherNetTraces = this.outputTraces.filter(
|
|
11762
|
+
(otherTrace) => otherTrace.mspPairId !== trace.mspPairId && otherTrace.globalConnNetId !== trace.globalConnNetId
|
|
11763
|
+
);
|
|
11764
|
+
const candidateByPath = /* @__PURE__ */ new Map();
|
|
11765
|
+
const completedReroutes = this.input.completedReroutes.filter(
|
|
11766
|
+
(completedReroute) => completedReroute.initialTrace.mspPairId === trace.mspPairId
|
|
11767
|
+
);
|
|
11768
|
+
for (const completedReroute of completedReroutes) {
|
|
11769
|
+
const initialReroutePath = simplifyPath(
|
|
11770
|
+
completedReroute.initialTrace.tracePath
|
|
11771
|
+
);
|
|
11772
|
+
const reroutedPath = simplifyPath(completedReroute.reroutedTracePath);
|
|
11773
|
+
const initialRerouteOverlapCount = detectTraceLabelOverlap({
|
|
11774
|
+
traces: [
|
|
11775
|
+
{ ...completedReroute.initialTrace, tracePath: initialReroutePath }
|
|
11776
|
+
],
|
|
11777
|
+
netLabels: this.input.netLabelPlacements
|
|
11778
|
+
}).length;
|
|
11779
|
+
const candidates = generateElbowTransitionSimplificationCandidates({
|
|
11780
|
+
trace: completedReroute.initialTrace,
|
|
11781
|
+
label: completedReroute.label,
|
|
11782
|
+
netLabelPlacements: this.input.netLabelPlacements,
|
|
11783
|
+
paddingBuffer: this.input.paddingBuffer,
|
|
11784
|
+
detourCount: completedReroute.detourCount
|
|
11785
|
+
});
|
|
11786
|
+
for (const candidate of candidates) {
|
|
11787
|
+
const simplifiedCandidate = simplifyPath(candidate);
|
|
11788
|
+
const candidateTrace = {
|
|
11789
|
+
...completedReroute.initialTrace,
|
|
11790
|
+
tracePath: simplifiedCandidate
|
|
11791
|
+
};
|
|
11792
|
+
const candidateOverlapCount = detectTraceLabelOverlap({
|
|
11793
|
+
traces: [candidateTrace],
|
|
11794
|
+
netLabels: this.input.netLabelPlacements
|
|
11795
|
+
}).length;
|
|
11796
|
+
const isSimplerEquivalentReroute = candidateOverlapCount < initialRerouteOverlapCount && Math.abs(
|
|
11797
|
+
getPathLength4(simplifiedCandidate) - getPathLength4(reroutedPath)
|
|
11798
|
+
) < PATH_LENGTH_EPSILON2 && simplifiedCandidate.length < reroutedPath.length && preservesLabelAnchors(
|
|
11799
|
+
this.input.netLabelPlacements,
|
|
11800
|
+
[completedReroute.initialTrace],
|
|
11801
|
+
[candidateTrace]
|
|
11802
|
+
);
|
|
11803
|
+
if (isSimplerEquivalentReroute) {
|
|
11804
|
+
candidateByPath.set(
|
|
11805
|
+
simplifiedCandidate.map((point) => `${point.x},${point.y}`).join(";"),
|
|
11806
|
+
simplifiedCandidate
|
|
11807
|
+
);
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
}
|
|
11811
|
+
const initialOverlapIds = new Set(
|
|
11812
|
+
initialOverlaps.map(
|
|
11813
|
+
({ label }) => `${label.globalConnNetId}:${label.netId}`
|
|
11814
|
+
)
|
|
11815
|
+
);
|
|
11816
|
+
const initialPathLength = getPathLength4(tracePath);
|
|
11817
|
+
const validCandidates = [...candidateByPath.values()].filter(
|
|
11818
|
+
(candidatePath) => {
|
|
11819
|
+
const candidateTrace = { ...trace, tracePath: candidatePath };
|
|
11820
|
+
const candidateOverlaps = detectTraceLabelOverlap({
|
|
11821
|
+
traces: [candidateTrace],
|
|
11822
|
+
netLabels: this.input.netLabelPlacements
|
|
11823
|
+
});
|
|
11824
|
+
const candidateOnlyKeepsExistingOverlaps = candidateOverlaps.every(
|
|
11825
|
+
({ label }) => initialOverlapIds.has(`${label.globalConnNetId}:${label.netId}`)
|
|
11826
|
+
);
|
|
11827
|
+
const reducesCollisions = candidateOverlaps.length < initialOverlaps.length;
|
|
11828
|
+
const simplifiesGeometry = candidateOverlaps.length === initialOverlaps.length && getPathLength4(candidatePath) <= initialPathLength + PATH_LENGTH_EPSILON2 && candidatePath.length < tracePath.length;
|
|
11829
|
+
return candidateOnlyKeepsExistingOverlaps && (reducesCollisions || simplifiesGeometry) && preservesLabelAnchors(
|
|
11830
|
+
this.input.netLabelPlacements,
|
|
11831
|
+
[trace],
|
|
11832
|
+
[candidateTrace]
|
|
11833
|
+
) && !isPathCollidingWithObstacles(candidatePath, this.obstacles) && !doesPathCoincideWithTraces(candidatePath, otherNetTraces);
|
|
11834
|
+
}
|
|
11835
|
+
);
|
|
11836
|
+
const getOverlapCount = (candidatePath) => detectTraceLabelOverlap({
|
|
11837
|
+
traces: [{ ...trace, tracePath: candidatePath }],
|
|
11838
|
+
netLabels: this.input.netLabelPlacements
|
|
11839
|
+
}).length;
|
|
11840
|
+
validCandidates.sort((a, b) => {
|
|
11841
|
+
const overlapDifference = getOverlapCount(a) - getOverlapCount(b);
|
|
11842
|
+
if (overlapDifference !== 0) return overlapDifference;
|
|
11843
|
+
return getPathLength4(a) - getPathLength4(b) || a.length - b.length;
|
|
11844
|
+
});
|
|
11845
|
+
const bestCandidate = validCandidates[0];
|
|
11846
|
+
if (!bestCandidate) return;
|
|
11847
|
+
this.outputTraces[traceIndex] = { ...trace, tracePath: bestCandidate };
|
|
11848
|
+
this.stats.simplifiedTraceCount = (this.stats.simplifiedTraceCount ?? 0) + 1;
|
|
11849
|
+
}
|
|
11850
|
+
getOutput() {
|
|
11851
|
+
return {
|
|
11852
|
+
traces: this.outputTraces,
|
|
11853
|
+
netLabelPlacements: this.input.netLabelPlacements
|
|
11854
|
+
};
|
|
11855
|
+
}
|
|
11856
|
+
visualize() {
|
|
11857
|
+
const graphics = visualizeInputProblem(this.input.inputProblem);
|
|
11858
|
+
graphics.lines ??= [];
|
|
11859
|
+
for (const trace of this.outputTraces) {
|
|
11860
|
+
graphics.lines.push({ points: trace.tracePath, strokeColor: "purple" });
|
|
11861
|
+
}
|
|
11862
|
+
return graphics;
|
|
11863
|
+
}
|
|
11864
|
+
};
|
|
11865
|
+
|
|
11528
11866
|
// lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
|
|
11529
11867
|
function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
|
|
11530
11868
|
return {
|
|
@@ -11555,6 +11893,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11555
11893
|
netLabelTraceCollisionSolver;
|
|
11556
11894
|
traceCleanupSolver2;
|
|
11557
11895
|
netLabelNetLabelCollisionSolver;
|
|
11896
|
+
traceElbowTransitionSimplificationSolver;
|
|
11897
|
+
preAlignmentTraceElbowTransitionSimplificationSolver;
|
|
11898
|
+
finalTraceElbowTransitionSimplificationSolver;
|
|
11558
11899
|
sameNetJunctionAlignmentSolver;
|
|
11559
11900
|
startTimeOfPhase;
|
|
11560
11901
|
endTimeOfPhase;
|
|
@@ -11676,8 +12017,24 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11676
12017
|
];
|
|
11677
12018
|
}
|
|
11678
12019
|
),
|
|
12020
|
+
definePipelineStep(
|
|
12021
|
+
"traceElbowTransitionSimplificationSolver",
|
|
12022
|
+
TraceElbowTransitionSimplificationSolver,
|
|
12023
|
+
(instance) => {
|
|
12024
|
+
const overlapAvoidanceOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
|
|
12025
|
+
return [
|
|
12026
|
+
{
|
|
12027
|
+
inputProblem: instance.inputProblem,
|
|
12028
|
+
traces: overlapAvoidanceOutput.traces,
|
|
12029
|
+
completedReroutes: overlapAvoidanceOutput.completedReroutes,
|
|
12030
|
+
netLabelPlacements: instance.traceLabelOverlapAvoidanceSolver.netLabelPlacements,
|
|
12031
|
+
paddingBuffer: 0.1
|
|
12032
|
+
}
|
|
12033
|
+
];
|
|
12034
|
+
}
|
|
12035
|
+
),
|
|
11679
12036
|
definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
|
|
11680
|
-
const prevSolverOutput = instance.
|
|
12037
|
+
const prevSolverOutput = instance.traceElbowTransitionSimplificationSolver.getOutput();
|
|
11681
12038
|
const traces = prevSolverOutput.traces;
|
|
11682
12039
|
const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
|
|
11683
12040
|
return [
|
|
@@ -11779,11 +12136,27 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11779
12136
|
}
|
|
11780
12137
|
]
|
|
11781
12138
|
),
|
|
12139
|
+
definePipelineStep(
|
|
12140
|
+
"preAlignmentTraceElbowTransitionSimplificationSolver",
|
|
12141
|
+
TraceElbowTransitionSimplificationSolver,
|
|
12142
|
+
(instance) => {
|
|
12143
|
+
const collisionOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
|
|
12144
|
+
return [
|
|
12145
|
+
{
|
|
12146
|
+
inputProblem: instance.inputProblem,
|
|
12147
|
+
traces: collisionOutput.traces,
|
|
12148
|
+
completedReroutes: collisionOutput.completedReroutes,
|
|
12149
|
+
netLabelPlacements: collisionOutput.netLabelPlacements,
|
|
12150
|
+
paddingBuffer: 0.1
|
|
12151
|
+
}
|
|
12152
|
+
];
|
|
12153
|
+
}
|
|
12154
|
+
),
|
|
11782
12155
|
definePipelineStep(
|
|
11783
12156
|
"traceCleanupSolver2",
|
|
11784
12157
|
TraceCleanupSolver,
|
|
11785
12158
|
(instance) => {
|
|
11786
|
-
const collisionOutput = instance.
|
|
12159
|
+
const collisionOutput = instance.preAlignmentTraceElbowTransitionSimplificationSolver.getOutput();
|
|
11787
12160
|
const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
|
|
11788
12161
|
return [
|
|
11789
12162
|
{
|
|
@@ -11836,16 +12209,35 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
|
|
|
11836
12209
|
];
|
|
11837
12210
|
}
|
|
11838
12211
|
),
|
|
12212
|
+
definePipelineStep(
|
|
12213
|
+
"finalTraceElbowTransitionSimplificationSolver",
|
|
12214
|
+
TraceElbowTransitionSimplificationSolver,
|
|
12215
|
+
(instance) => {
|
|
12216
|
+
const collisionOutput = instance.netLabelTraceCollisionSolver.getOutput();
|
|
12217
|
+
return [
|
|
12218
|
+
{
|
|
12219
|
+
inputProblem: instance.inputProblem,
|
|
12220
|
+
traces: collisionOutput.traces,
|
|
12221
|
+
completedReroutes: collisionOutput.completedReroutes,
|
|
12222
|
+
netLabelPlacements: collisionOutput.netLabelPlacements,
|
|
12223
|
+
paddingBuffer: 0.1
|
|
12224
|
+
}
|
|
12225
|
+
];
|
|
12226
|
+
}
|
|
12227
|
+
),
|
|
11839
12228
|
definePipelineStep(
|
|
11840
12229
|
"netLabelNetLabelCollisionSolver",
|
|
11841
12230
|
NetLabelNetLabelCollisionSolver,
|
|
11842
|
-
(instance) =>
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
12231
|
+
(instance) => {
|
|
12232
|
+
const simplificationOutput = instance.finalTraceElbowTransitionSimplificationSolver.getOutput();
|
|
12233
|
+
return [
|
|
12234
|
+
{
|
|
12235
|
+
inputProblem: instance.inputProblem,
|
|
12236
|
+
traces: simplificationOutput.traces,
|
|
12237
|
+
netLabelPlacements: simplificationOutput.netLabelPlacements
|
|
12238
|
+
}
|
|
12239
|
+
];
|
|
12240
|
+
}
|
|
11849
12241
|
),
|
|
11850
12242
|
definePipelineStep(
|
|
11851
12243
|
"sameNetJunctionAlignmentSolver",
|